LeastUpperBoundTest.java
/*
* Copyright (C) 2013-2024 The JavaParser Team.
*
* This file is part of JavaParser.
*
* JavaParser can be used either under the terms of
* a) the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* b) the terms of the Apache License
*
* You should have received a copy of both licenses in LICENCE.LGPL and
* LICENCE.APACHE. Please refer to those files for details.
*
* JavaParser is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*/
package com.github.javaparser.symbolsolver.resolution.typeinference;
import static org.junit.jupiter.api.Assertions.*;
import com.github.javaparser.ParserConfiguration;
import com.github.javaparser.StaticJavaParser;
import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
import com.github.javaparser.ast.body.EnumDeclaration;
import com.github.javaparser.resolution.TypeSolver;
import com.github.javaparser.resolution.UnsolvedSymbolException;
import com.github.javaparser.resolution.declarations.ResolvedReferenceTypeDeclaration;
import com.github.javaparser.resolution.model.typesystem.NullType;
import com.github.javaparser.resolution.model.typesystem.ReferenceTypeImpl;
import com.github.javaparser.resolution.types.ResolvedType;
import com.github.javaparser.resolution.types.ResolvedWildcard;
import com.github.javaparser.symbolsolver.JavaSymbolSolver;
import com.github.javaparser.symbolsolver.resolution.typesolvers.ReflectionTypeSolver;
import com.google.common.collect.Lists;
import java.io.FileNotFoundException;
import java.io.IOError;
import java.io.IOException;
import java.io.Serializable;
import java.net.URISyntaxException;
import java.util.*;
import java.util.stream.Collectors;
import org.junit.jupiter.api.*;
class LeastUpperBoundTest {
private TypeSolver typeSolver;
@BeforeAll
static void setUpBeforeClass() throws Exception {
ParserConfiguration configuration =
new ParserConfiguration().setSymbolResolver(new JavaSymbolSolver(new ReflectionTypeSolver()));
// Setup parser
StaticJavaParser.setConfiguration(configuration);
}
@AfterAll
static void tearDownAfterClass() throws Exception {}
@BeforeEach
void setUp() throws Exception {
typeSolver = new ReflectionTypeSolver();
}
@AfterEach
void tearDown() throws Exception {}
@Test
public void lub_of_one_element_is_itself() {
ResolvedType exception = type(Exception.class.getCanonicalName());
ResolvedType lub = leastUpperBound(exception);
assertEquals(lub, exception);
}
@Test
public void lub_of_one_null_element_is_itself() {
ResolvedType lub = leastUpperBound(NullType.INSTANCE);
assertEquals(lub, lub);
}
@Test
public void lub_should_fail_if_no_type_provided() {
try {
ResolvedType lub = leastUpperBound(new ResolvedType[] {});
fail("should have failed");
} catch (Exception e) {
assertTrue(e instanceof IllegalArgumentException);
}
}
@Test
public void lub_with_shared_supertypes() {
ResolvedType exception = type(Exception.class.getCanonicalName());
ResolvedType error = type(Error.class.getCanonicalName());
ResolvedType expected = type(Throwable.class.getCanonicalName());
ResolvedType lub = leastUpperBound(exception, error);
assertEquals(expected, lub);
}
@Test
public void lub_with_shared_supertypes_from_interface() {
ResolvedType exception = type(Exception.class.getCanonicalName());
ResolvedType throwable = type(Throwable.class.getCanonicalName());
ResolvedType serializable = type(Serializable.class.getCanonicalName());
ResolvedType expected = type(Serializable.class.getCanonicalName());
ResolvedType lub = leastUpperBound(exception, throwable, serializable);
assertEquals(expected, lub);
}
@Test
public void lub_with_shared_supertypes_from_serializable() {
ResolvedType exception = type(Exception.class.getCanonicalName());
ResolvedType string = type(String.class.getCanonicalName());
ResolvedType expected = type(Serializable.class.getCanonicalName());
ResolvedType lub = leastUpperBound(exception, string);
assertEquals(expected, lub);
}
@Test
public void lub_with_hierarchy_of_supertypes1() {
ResolvedType exception = type(Exception.class.getCanonicalName());
ResolvedType ioexception = type(IOException.class.getCanonicalName());
ResolvedType expected = type(Exception.class.getCanonicalName());
ResolvedType lub = leastUpperBound(exception, ioexception);
assertEquals(expected, lub);
}
@Test
public void lub_with_hierarchy_of_supertypes2() {
ResolvedType error = type(Error.class.getCanonicalName());
ResolvedType ioexception = type(IOException.class.getCanonicalName());
ResolvedType ioerror = type(IOError.class.getCanonicalName());
ResolvedType expected = type(Throwable.class.getCanonicalName());
ResolvedType lub = leastUpperBound(error, ioexception, ioerror);
assertEquals(expected, lub);
}
@Test
public void lub_with_no_shared_supertypes_exception_object() {
List<ResolvedType> types = declaredTypes("class A extends Exception {}", "class B {}");
ResolvedType a = types.get(0);
ResolvedType b = types.get(1);
ResolvedType lub = leastUpperBound(a, b);
ResolvedType expected = type("java.lang.Object");
assertEquals(expected, lub);
}
@Test
public void lub_approximation_inheritance_and_multiple_bounds() {
List<ResolvedType> types = declaredTypes(
"class A implements I1, I2 {}", "class B implements I2, I1 {}", "interface I1 {}", "interface I2 {}");
ResolvedType a = types.get(0);
ResolvedType b = types.get(1);
ResolvedType lub = leastUpperBound(a, b);
ResolvedType expected = types.get(2);
// should be <I1 & I2>, not only I1 (first interface of first type analyzed)
assertEquals(expected, lub);
}
@Test
public void lub_approximation_with_complexe_inheritance() {
ResolvedType expected = type(Exception.class.getCanonicalName());
// java.lang.Object/java.lang.Throwable/java.lang.Exception/java.net.URISyntaxException
ResolvedType uriSyntaxException = type(URISyntaxException.class.getCanonicalName());
// java.lang.Object//java.lang.Throwable/java.lang.Exception/java.io.IOException/java.io.FileNotFoundException
ResolvedType fileNotFoundException = type(FileNotFoundException.class.getCanonicalName());
ResolvedType lub = leastUpperBound(uriSyntaxException, fileNotFoundException);
assertEquals(expected, lub);
}
@Test
public void lub_with_unknown_inheritance() {
List<ResolvedType> types = declaredTypes("class A extends Exception {}", "class B extends UnknownException {}");
ResolvedType a = types.get(0);
ResolvedType b = types.get(1);
try {
ResolvedType lub = leastUpperBound(a, b);
fail("UnknownException cannot be resolved");
} catch (UnsolvedSymbolException e) {
assertTrue(e instanceof UnsolvedSymbolException);
}
}
@Test
public void lub_of_null_and_object() {
ResolvedType nullType = NullType.INSTANCE;
ResolvedType stringType = type(String.class.getCanonicalName());
ResolvedType expected = type(String.class.getCanonicalName());
ResolvedType lub = leastUpperBound(nullType, stringType);
assertEquals(expected, lub);
}
@Test
public void lub_of_enum() {
ResolvedType type = type("java.math.RoundingMode");
ResolvedReferenceTypeDeclaration typeDeclaration =
type.asReferenceType().getTypeDeclaration().get();
List<ResolvedType> constanteTypes = typeDeclaration.asEnum().getEnumConstants().stream()
.map(enumConst -> enumConst.getType())
.collect(Collectors.toList());
ResolvedType expected = constanteTypes.get(0);
ResolvedType lub = leastUpperBound(constanteTypes.get(0), constanteTypes.get(1));
assertEquals(
expected.asReferenceType().describe(), lub.asReferenceType().describe());
}
@Test
public void lub_of_generics_with_shared_super_class() {
List<ResolvedType> types = declaredTypes(
"class A extends Exception {}",
"class B extends Exception implements I1<Exception> {}",
"interface I1<T> {}");
ResolvedType expected = type(Exception.class.getCanonicalName());
ResolvedType lub = leastUpperBound(types.get(0), types.get(1));
assertEquals(expected, lub);
}
@Test
public void lub_of_generics_with_inheritance() {
List<ResolvedType> types =
declaredTypes("class A<T> extends java.util.List<T> {}", "class B extends A<String> {}");
ResolvedType expected = types.get(0);
ResolvedType lub = leastUpperBound(types.get(0), types.get(1));
ResolvedType erased = lub.erasure();
assertEquals(expected.erasure(), erased);
assertTrue(!lub.asReferenceType().typeParametersValues().isEmpty());
}
@Test
void lub_of_generics_with_different_parametrized_type() {
ResolvedType list1 = genericType(List.class.getCanonicalName(), String.class.getCanonicalName());
ResolvedType list2 = genericType(List.class.getCanonicalName(), Object.class.getCanonicalName());
ResolvedType expected = genericType(List.class.getCanonicalName(), Object.class.getCanonicalName());
ResolvedType lub = leastUpperBound(list1, list2);
assertEquals(expected, lub);
}
@Test
void lub_of_generics_with_different_parametrized_type2() {
ResolvedType list1 = genericType(HashSet.class.getCanonicalName(), String.class.getCanonicalName());
ResolvedType list2 = genericType(LinkedHashSet.class.getCanonicalName(), String.class.getCanonicalName());
ResolvedType expected = genericType(HashSet.class.getCanonicalName(), String.class.getCanonicalName());
ResolvedType lub = leastUpperBound(list1, list2);
assertEquals(expected, lub);
}
@Test
void lub_of_generics_with_different_bound_on_same_type() {
ResolvedType list1 =
genericType(List.class.getCanonicalName(), extendsBound(Exception.class.getCanonicalName()));
ResolvedType list2 = genericType(List.class.getCanonicalName(), superBound(Exception.class.getCanonicalName()));
ResolvedType expected = genericType(List.class.getCanonicalName(), Exception.class.getCanonicalName());
ResolvedType lub = leastUpperBound(list1, list2);
assertEquals(expected.describe(), lub.describe());
}
@Test
void lub_of_generics_with_bounded_type_in_hierarchy() {
ResolvedType list1 = genericType(List.class.getCanonicalName(), Number.class.getCanonicalName());
ResolvedType list2 = genericType(List.class.getCanonicalName(), Integer.class.getCanonicalName());
ResolvedType expected =
genericType(List.class.getCanonicalName(), extendsBound(Number.class.getCanonicalName()));
ResolvedType lub = leastUpperBound(list1, list2);
assertEquals(expected.describe(), lub.describe());
}
@Test
@Disabled("Waiting for generic type inheritance")
// we have to find the inheritance tree for List<? extends Integer> or List<? extends Number>
void lub_of_generics_with_upper_bounded_type_in_hierarchy() {
ResolvedType list1 = genericType(List.class.getCanonicalName(), extendsBound(Number.class.getCanonicalName()));
ResolvedType list2 = genericType(List.class.getCanonicalName(), extendsBound(Integer.class.getCanonicalName()));
ResolvedType expected =
genericType(List.class.getCanonicalName(), extendsBound(Number.class.getCanonicalName()));
ResolvedType lub = leastUpperBound(list1, list2);
assertEquals(expected.describe(), lub.describe());
}
@Test
@Disabled("Waiting for generic type resolution")
public void lub_of_generics_with_raw_type() {
List<ResolvedType> types = declaredTypes(
"class Parent<X> {}",
"class Child<Y> extends Parent<Y> {}",
"class ChildString extends Child<String> {}",
"class ChildRaw extends Child {}");
ResolvedType ChildString = types.get(2);
ResolvedType ChildRaw = types.get(3);
ResolvedType lub = leastUpperBound(ChildString, ChildRaw);
ResolvedType expected = types.get(1);
assertEquals(expected, lub);
}
@Test
@Disabled("Waiting for generic type resolution")
public void lub_of_generics_with_inheritance_and_wildcard() {
List<ResolvedType> types = declaredTypes(
"class Parent<X> {}",
"class Child<Y> extends Parent<Y> {}",
"class Other<Z> {}",
"class A {}",
"class ChildP extends Parent<Other<? extends A>> {}",
"class ChildC extends Child<Other<? extends A>> {}");
ResolvedType ChildP = types.get(4);
ResolvedType childC = types.get(5);
ResolvedType lub = leastUpperBound(ChildP, childC);
System.out.println(lub.describe());
assertEquals("Parent<Other<? extends A>>", lub.describe());
}
@Test
@Disabled("Waiting for generic type resolution")
public void lub_of_generics_without_loop() {
List<ResolvedType> types = declaredTypes(
"class Parent<X1, X2> {}",
"class Child<Y1, Y2> extends Parent<Y1, Y2> {}",
"class GrandChild<Z1, Z2> extends Child<Z1, Z2> {}",
"class A {}",
"class B extends A {}",
"class C extends A {}",
"class D extends C {}",
"class ChildBA extends Child<B, A> {}",
"class ChildCA extends Child<C, A> {}",
"class GrandChildDA extends GrandChild<D, D> {}");
ResolvedType childBA = types.get(7);
ResolvedType childCA = types.get(8);
ResolvedType grandChildDD = types.get(9);
ResolvedType lub = leastUpperBound(childBA, childCA, grandChildDD);
System.out.println(lub.describe());
}
@Test
@Disabled("Waiting for generic type resolution")
public void lub_of_generics_without_loop2() {
List<ResolvedType> typesFromInput = declaredTypes(
"class Parent<X> {}",
"class Child<Y> extends Parent<Y> {}",
"class Other<Z> {}",
"class A {}",
"class ChildP extends Parent<Other<? extends A>> {}",
"class ChildC extends Child<Other<? extends A>> {}");
ResolvedType ChildP = typesFromInput.get(4);
ResolvedType childC = typesFromInput.get(5);
ResolvedType lub = leastUpperBound(ChildP, childC);
System.out.println(lub.describe());
}
@Test
@Disabled("Waiting for generic type resolution")
public void lub_of_generics_infinite_types() {
List<ResolvedType> types = declaredTypes(
"class Parent<X> {}",
"class Child<Y> extends Parent<Y> {}",
"class ChildInteger extends Child<Integer> {}",
"class ChildString extends Child<String> {}");
ResolvedType childInteger = types.get(2);
ResolvedType childString = types.get(3);
ResolvedType lub = leastUpperBound(childInteger, childString);
System.out.println(lub.describe());
}
private List<ResolvedType> types(String... types) {
return Arrays.stream(types).map(type -> type(type)).collect(Collectors.toList());
}
private ResolvedType type(String type) {
return new ReferenceTypeImpl(typeSolver.solveType(type));
}
private ResolvedType genericType(String type, String... parameterTypes) {
return new ReferenceTypeImpl(typeSolver.solveType(type), types(parameterTypes));
}
private ResolvedType genericType(String type, ResolvedType... parameterTypes) {
return new ReferenceTypeImpl(typeSolver.solveType(type), Arrays.asList(parameterTypes));
}
private ResolvedType extendsBound(String type) {
return ResolvedWildcard.extendsBound(type(type));
}
private ResolvedType superBound(String type) {
return ResolvedWildcard.superBound(type(type));
}
private ResolvedType unbound() {
return ResolvedWildcard.UNBOUNDED;
}
private Set<ResolvedType> toSet(ResolvedType... resolvedTypes) {
return new HashSet<>(Arrays.asList(resolvedTypes));
}
private ResolvedType leastUpperBound(ResolvedType... types) {
return TypeHelper.leastUpperBound(toSet(types));
}
private List<ResolvedType> declaredTypes(String... lines) {
CompilationUnit tree = treeOf(lines);
List<ResolvedType> results = Lists.newLinkedList();
for (ClassOrInterfaceDeclaration classTree : tree.findAll(ClassOrInterfaceDeclaration.class)) {
results.add(new ReferenceTypeImpl(classTree.resolve()));
}
return results;
}
private List<ResolvedType> declaredEnumTypes(String... lines) {
CompilationUnit tree = treeOf(lines);
List<ResolvedType> results = Lists.newLinkedList();
for (EnumDeclaration classTree : tree.findAll(EnumDeclaration.class)) {
results.add(new ReferenceTypeImpl(classTree.resolve()));
}
return results;
}
private CompilationUnit treeOf(String... lines) {
StringBuilder builder = new StringBuilder();
for (String line : lines) {
builder.append(line).append(System.lineSeparator());
}
return StaticJavaParser.parse(builder.toString());
}
}