blob: 078ef513ff60c4dc8949bdc51ec7410d35ff6847 [file] [log] [blame]
/*
* Copyright 2019 Google LLC
*
* 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 com.google.auto.value.processor;
import static com.google.common.truth.Truth.assertThat;
import com.google.common.collect.ImmutableMap;
import com.google.common.truth.Expect;
import com.google.testing.compile.CompilationRule;
import java.util.List;
import java.util.Map;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.ElementFilter;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@RunWith(JUnit4.class)
public class TypeVariablesTest {
@ClassRule public static final CompilationRule compilationRule = new CompilationRule();
@Rule public final Expect expect = Expect.create();
private static Elements elementUtils;
private static Types typeUtils;
@BeforeClass
public static void setUpClass() {
elementUtils = compilationRule.getElements();
typeUtils = compilationRule.getTypes();
}
abstract static class Source1 {
abstract String getFoo();
}
abstract static class Target1 {}
@Test
public void noTypeParameters() {
TypeElement source1 = elementUtils.getTypeElement(Source1.class.getCanonicalName());
TypeElement target1 = elementUtils.getTypeElement(Target1.class.getCanonicalName());
List<ExecutableElement> sourceMethods = ElementFilter.methodsIn(source1.getEnclosedElements());
Map<ExecutableElement, TypeMirror> types =
TypeVariables.rewriteReturnTypes(elementUtils, typeUtils, sourceMethods, source1, target1);
assertThat(types).containsExactly(sourceMethods.get(0), sourceMethods.get(0).getReturnType());
}
abstract static class Source2<T> {
abstract List<T> getFoo();
}
abstract static class Target2<T> {
abstract void setFoo(List<T> list);
}
@Test
public void simpleTypeParameter() {
TypeElement source2 = elementUtils.getTypeElement(Source2.class.getCanonicalName());
TypeElement target2 = elementUtils.getTypeElement(Target2.class.getCanonicalName());
List<ExecutableElement> sourceMethods = ElementFilter.methodsIn(source2.getEnclosedElements());
Map<ExecutableElement, TypeMirror> types =
TypeVariables.rewriteReturnTypes(elementUtils, typeUtils, sourceMethods, source2, target2);
List<ExecutableElement> targetMethods = ElementFilter.methodsIn(target2.getEnclosedElements());
TypeMirror setFooParameter = targetMethods.get(0).getParameters().get(0).asType();
ExecutableElement getFoo = sourceMethods.get(0);
TypeMirror originalGetFooReturn = getFoo.getReturnType();
TypeMirror rewrittenGetFooReturn = types.get(getFoo);
assertThat(typeUtils.isAssignable(setFooParameter, originalGetFooReturn)).isFalse();
assertThat(typeUtils.isAssignable(setFooParameter, rewrittenGetFooReturn)).isTrue();
}
abstract static class Source3<T extends Comparable<T>, U> {
abstract Map<T, ? extends U> getFoo();
}
abstract static class Target3<T extends Comparable<T>, U> {
abstract void setFoo(Map<T, ? extends U> list);
}
@Test
public void hairyTypeParameters() {
TypeElement source3 = elementUtils.getTypeElement(Source3.class.getCanonicalName());
TypeElement target3 = elementUtils.getTypeElement(Target3.class.getCanonicalName());
List<ExecutableElement> sourceMethods = ElementFilter.methodsIn(source3.getEnclosedElements());
Map<ExecutableElement, TypeMirror> types =
TypeVariables.rewriteReturnTypes(elementUtils, typeUtils, sourceMethods, source3, target3);
List<ExecutableElement> targetMethods = ElementFilter.methodsIn(target3.getEnclosedElements());
TypeMirror setFooParameter = targetMethods.get(0).getParameters().get(0).asType();
ExecutableElement getFoo = sourceMethods.get(0);
TypeMirror originalGetFooReturn = getFoo.getReturnType();
TypeMirror rewrittenGetFooReturn = types.get(getFoo);
assertThat(typeUtils.isAssignable(setFooParameter, originalGetFooReturn)).isFalse();
assertThat(typeUtils.isAssignable(setFooParameter, rewrittenGetFooReturn)).isTrue();
}
abstract static class Outer<T, U extends T> {
abstract Map<T, U> getFoo();
abstract List<? extends T> getBar();
abstract static class Inner<T, U extends T> {
abstract void setFoo(Map<T, U> foo);
abstract void setBar(List<? extends T> bar);
}
}
@Test
public void nestedClasses() {
TypeElement outer = elementUtils.getTypeElement(Outer.class.getCanonicalName());
TypeElement inner = elementUtils.getTypeElement(Outer.Inner.class.getCanonicalName());
List<ExecutableElement> outerMethods = ElementFilter.methodsIn(outer.getEnclosedElements());
Map<ExecutableElement, TypeMirror> types =
TypeVariables.rewriteReturnTypes(elementUtils, typeUtils, outerMethods, outer, inner);
List<ExecutableElement> innerMethods = ElementFilter.methodsIn(inner.getEnclosedElements());
ExecutableElement getFoo = methodNamed(outerMethods, "getFoo");
ExecutableElement getBar = methodNamed(outerMethods, "getBar");
ExecutableElement setFoo = methodNamed(innerMethods, "setFoo");
ExecutableElement setBar = methodNamed(innerMethods, "setBar");
TypeMirror setFooParameter = setFoo.getParameters().get(0).asType();
TypeMirror originalGetFooReturn = getFoo.getReturnType();
TypeMirror rewrittenGetFooReturn = types.get(getFoo);
assertThat(typeUtils.isAssignable(setFooParameter, originalGetFooReturn)).isFalse();
assertThat(typeUtils.isAssignable(setFooParameter, rewrittenGetFooReturn)).isTrue();
TypeMirror setBarParameter = setBar.getParameters().get(0).asType();
TypeMirror originalGetBarReturn = getBar.getReturnType();
TypeMirror rewrittenGetBarReturn = types.get(getBar);
assertThat(typeUtils.isAssignable(setBarParameter, originalGetBarReturn)).isFalse();
assertThat(typeUtils.isAssignable(setBarParameter, rewrittenGetBarReturn)).isTrue();
}
@Test
public void canAssignStaticMethodResult() {
TypeElement immutableMap = elementUtils.getTypeElement(ImmutableMap.class.getCanonicalName());
TypeElement string = elementUtils.getTypeElement(String.class.getCanonicalName());
TypeElement integer = elementUtils.getTypeElement(Integer.class.getCanonicalName());
TypeElement number = elementUtils.getTypeElement(Number.class.getCanonicalName());
TypeMirror immutableMapStringNumber =
typeUtils.getDeclaredType(immutableMap, string.asType(), number.asType());
TypeMirror immutableMapStringInteger =
typeUtils.getDeclaredType(immutableMap, string.asType(), integer.asType());
TypeElement map = elementUtils.getTypeElement(Map.class.getCanonicalName());
TypeMirror erasedMap = typeUtils.erasure(map.asType());
// If the target type is ImmutableMap<String, Number> then we should be able to use
// static <K, V> ImmutableMap<K, V> ImmutableMap.copyOf(Map<? extends K, ? extends V>)
// with a parameter of type ImmutableMap<String, Integer>.
List<ExecutableElement> immutableMapMethods =
ElementFilter.methodsIn(immutableMap.getEnclosedElements());
ExecutableElement copyOf = methodNamed(immutableMapMethods, "copyOf", erasedMap);
expect.that(
TypeVariables.canAssignStaticMethodResult(
copyOf, immutableMapStringInteger, immutableMapStringNumber, typeUtils))
.isTrue();
expect.that(
TypeVariables.canAssignStaticMethodResult(
copyOf, immutableMapStringNumber, immutableMapStringInteger, typeUtils))
.isFalse();
}
private static ExecutableElement methodNamed(List<ExecutableElement> methods, String name) {
return methods.stream().filter(m -> m.getSimpleName().contentEquals(name)).findFirst().get();
}
private static ExecutableElement methodNamed(
List<ExecutableElement> methods, String name, TypeMirror erasedParameterType) {
return methods.stream()
.filter(m -> m.getSimpleName().contentEquals(name))
.filter(m -> m.getParameters().size() == 1)
.filter(m -> typeUtils.isSameType(
erasedParameterType, typeUtils.erasure(m.getParameters().get(0).asType())))
.findFirst()
.get();
}
}