blob: 68a24fb7629e1fd16cfbf8b9529937110ed330de [file] [log] [blame]
/*
* Copyright 2012 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 static com.google.common.truth.Truth.assertWithMessage;
import com.google.testing.compile.CompilationRule;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.TypeParameterElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.type.TypeVariable;
import javax.lang.model.util.ElementFilter;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/**
* Tests for {@link TypeSimplifier}.
*
* @author emcmanus@google.com (Éamonn McManus)
*/
@RunWith(JUnit4.class)
public class TypeSimplifierTest {
@Rule public final CompilationRule compilationRule = new CompilationRule();
private Types typeUtils;
private Elements elementUtils;
@Before
public void setUp() {
typeUtils = compilationRule.getTypes();
elementUtils = compilationRule.getElements();
}
private static class Erasure<T> {
int intNo;
boolean booleanNo;
int[] intArrayNo;
String stringNo;
String[] stringArrayNo;
@SuppressWarnings("rawtypes")
List rawListNo;
List<?> listOfQueryNo;
List<? extends Object> listOfQueryExtendsObjectNo;
Map<?, ?> mapQueryToQueryNo;
List<String> listOfStringYes;
List<? extends String> listOfQueryExtendsStringYes;
List<? super String> listOfQuerySuperStringYes;
List<T> listOfTypeVarYes;
List<? extends T> listOfQueryExtendsTypeVarYes;
List<? super T> listOfQuerySuperTypeVarYes;
}
private abstract static class Wildcards {
abstract <T extends V, U extends T, V> Map<? extends T, ? super U> one();
abstract <T extends V, U extends T, V> Map<? extends T, ? super U> two();
}
/**
* This test shows why we need to have TypeMirrorSet. The mirror of java.lang.Object obtained from
* {@link Elements#getTypeElement Elements.getTypeElement("java.lang.Object")} does not compare
* equal to the mirror of the return type of Object.clone(), even though that is also
* java.lang.Object and {@link Types#isSameType} considers them the same.
*
* <p>There's no requirement that this test must pass and if it starts failing or doesn't work in
* another test environment then we can delete it. The specification of {@link TypeMirror#equals}
* explicitly says that it cannot be used for type equality, so even if this particular case stops
* being a problem (which means this test would fail), we would need TypeMirrorSet for complete
* correctness.
*/
@Test
public void testQuirkyTypeMirrors() {
TypeMirror objectMirror = objectMirror();
TypeMirror cloneReturnTypeMirror = cloneReturnTypeMirror();
assertThat(objectMirror).isNotEqualTo(cloneReturnTypeMirror);
assertThat(typeUtils.isSameType(objectMirror, cloneReturnTypeMirror)).isTrue();
}
@Test
public void testTypeMirrorSet() {
// Test the TypeMirrorSet methods. Resist the temptation to rewrite these in terms of
// Truth operations! For example, don't change assertThat(set.size()).isEqualTo(0) into
// assertThat(set).isEmpty(), because then we wouldn't be testing size().
TypeMirror objectMirror = objectMirror();
TypeMirror otherObjectMirror = cloneReturnTypeMirror();
Set<TypeMirror> set = new TypeMirrorSet();
assertThat(set.size()).isEqualTo(0);
assertThat(set.isEmpty()).isTrue();
boolean added = set.add(objectMirror);
assertThat(added).isTrue();
assertThat(set.size()).isEqualTo(1);
Set<TypeMirror> otherSet = typeMirrorSet(otherObjectMirror);
assertThat(otherSet).isEqualTo(set);
assertThat(set).isEqualTo(otherSet);
assertThat(otherSet.hashCode()).isEqualTo(set.hashCode());
assertThat(set.add(otherObjectMirror)).isFalse();
assertThat(set.contains(otherObjectMirror)).isTrue();
assertThat(set.contains(null)).isFalse();
assertThat(set.contains((Object) "foo")).isFalse();
assertThat(set.remove(null)).isFalse();
assertThat(set.remove((Object) "foo")).isFalse();
TypeElement list = typeElementOf(java.util.List.class);
TypeMirror listOfObjectMirror = typeUtils.getDeclaredType(list, objectMirror);
TypeMirror listOfOtherObjectMirror = typeUtils.getDeclaredType(list, otherObjectMirror);
assertThat(listOfObjectMirror.equals(listOfOtherObjectMirror)).isFalse();
assertThat(typeUtils.isSameType(listOfObjectMirror, listOfOtherObjectMirror)).isTrue();
added = set.add(listOfObjectMirror);
assertThat(added).isTrue();
assertThat(set.size()).isEqualTo(2);
assertThat(set.add(listOfOtherObjectMirror)).isFalse();
assertThat(set.contains(listOfOtherObjectMirror)).isTrue();
boolean removed = set.remove(listOfOtherObjectMirror);
assertThat(removed).isTrue();
assertThat(set.contains(listOfObjectMirror)).isFalse();
set.removeAll(otherSet);
assertThat(set.isEmpty()).isTrue();
}
@Test
public void testTypeMirrorSetWildcardCapture() {
// TODO(emcmanus): this test should really be in MoreTypesTest.
// This test checks the assumption made by MoreTypes that you can find the
// upper bounds of a TypeVariable tv like this:
// TypeParameterElement tpe = (TypeParameterElement) tv.asElement();
// List<? extends TypeMirror> bounds = tpe.getBounds();
// There was some doubt as to whether this would be true in exotic cases involving
// wildcard capture, but apparently it is.
// The methods one and two here have identical signatures:
// abstract <T extends V, U extends T, V> Map<? extends T, ? super U> name();
// Their return types should be considered equal by TypeMirrorSet. The capture of
// each return type is different from the original return type, but the two captures
// should compare equal to each other. We also add various other types like ? super U
// to the set to ensure that the implied calls to the equals and hashCode visitors
// don't cause a ClassCastException for TypeParameterElement.
TypeElement wildcardsElement = typeElementOf(Wildcards.class);
List<? extends ExecutableElement> methods =
ElementFilter.methodsIn(wildcardsElement.getEnclosedElements());
assertThat(methods).hasSize(2);
ExecutableElement one = methods.get(0);
ExecutableElement two = methods.get(1);
assertThat(one.getSimpleName().toString()).isEqualTo("one");
assertThat(two.getSimpleName().toString()).isEqualTo("two");
TypeMirrorSet typeMirrorSet = new TypeMirrorSet();
assertThat(typeMirrorSet.add(one.getReturnType())).isTrue();
assertThat(typeMirrorSet.add(two.getReturnType())).isFalse();
DeclaredType captureOne = (DeclaredType) typeUtils.capture(one.getReturnType());
assertThat(typeMirrorSet.add(captureOne)).isTrue();
DeclaredType captureTwo = (DeclaredType) typeUtils.capture(two.getReturnType());
assertThat(typeMirrorSet.add(captureTwo)).isFalse();
// Reminder: captureOne is Map<?#123 extends T, ?#456 super U>
TypeVariable extendsT = (TypeVariable) captureOne.getTypeArguments().get(0);
assertThat(typeMirrorSet.add(extendsT)).isTrue();
assertThat(typeMirrorSet.add(extendsT.getLowerBound())).isTrue(); // NoType
for (TypeMirror bound : ((TypeParameterElement) extendsT.asElement()).getBounds()) {
assertThat(typeMirrorSet.add(bound)).isTrue();
}
TypeVariable superU = (TypeVariable) captureOne.getTypeArguments().get(1);
assertThat(typeMirrorSet.add(superU)).isTrue();
assertThat(typeMirrorSet.add(superU.getLowerBound())).isTrue();
}
@Test
public void testPackageNameOfString() {
assertThat(TypeSimplifier.packageNameOf(typeElementOf(java.lang.String.class)))
.isEqualTo("java.lang");
}
@Test
public void testPackageNameOfMapEntry() {
assertThat(TypeSimplifier.packageNameOf(typeElementOf(java.util.Map.Entry.class)))
.isEqualTo("java.util");
}
// Test TypeSimplifier.isCastingUnchecked. We do this by examining the fields of the Erasure
// class. A field whose name ends with Yes has a type where
// isCastingUnchecked should return true, and one whose name ends with No has a type where
// isCastingUnchecked should return false.
@Test
public void testIsCastingUnchecked() {
TypeElement erasureClass = typeElementOf(Erasure.class);
List<VariableElement> fields = ElementFilter.fieldsIn(erasureClass.getEnclosedElements());
assertThat(fields).isNotEmpty();
for (VariableElement field : fields) {
String fieldName = field.getSimpleName().toString();
boolean expectUnchecked;
if (fieldName.endsWith("Yes")) {
expectUnchecked = true;
} else if (fieldName.endsWith("No")) {
expectUnchecked = false;
} else {
throw new AssertionError("Fields in Erasure class must end with Yes or No: " + fieldName);
}
TypeMirror fieldType = field.asType();
boolean actualUnchecked = TypeSimplifier.isCastingUnchecked(fieldType);
assertWithMessage("Unchecked-cast status for " + fieldType)
.that(actualUnchecked)
.isEqualTo(expectUnchecked);
}
}
private static Set<TypeMirror> typeMirrorSet(TypeMirror... typeMirrors) {
Set<TypeMirror> set = new TypeMirrorSet();
for (TypeMirror typeMirror : typeMirrors) {
assertThat(set.add(typeMirror)).isTrue();
}
return set;
}
private TypeMirror objectMirror() {
return typeMirrorOf(Object.class);
}
private TypeMirror cloneReturnTypeMirror() {
TypeElement object = typeElementOf(Object.class);
ExecutableElement clone = null;
for (Element element : object.getEnclosedElements()) {
if (element.getSimpleName().contentEquals("clone")) {
clone = (ExecutableElement) element;
break;
}
}
return clone.getReturnType();
}
private TypeElement typeElementOf(Class<?> c) {
return elementUtils.getTypeElement(c.getCanonicalName());
}
private TypeMirror typeMirrorOf(Class<?> c) {
return typeElementOf(c).asType();
}
}