/*
* SonarQube Java
* Copyright (C) 2012-2016 SonarSource SA
* mailto:contact AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of 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.
*
* This program 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.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.sonar.java.resolve;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import org.junit.Before;
import org.junit.Test;
import org.sonar.java.resolve.JavaSymbol.MethodJavaSymbol;
import org.sonar.java.resolve.WildCardType.BoundType;
import org.sonar.plugins.java.api.semantic.Type;
import java.util.ArrayList;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
public class TypeInferenceSolverTest {
private Symbols symbols;
private ParametrizedTypeCache parametrizedTypeCache;
private TypeInferenceSolver typeInferenceSolver;
private static TypeVariableJavaType T;
@Before
public void setUp() {
parametrizedTypeCache = new ParametrizedTypeCache();
symbols = new Symbols(new BytecodeCompleter(Lists.<java.io.File>newArrayList(), parametrizedTypeCache));
TypeSubstitutionSolver typeSubstitutionSolver = new TypeSubstitutionSolver(parametrizedTypeCache, symbols);
LeastUpperBound lub = new LeastUpperBound(typeSubstitutionSolver, parametrizedTypeCache, symbols);
typeInferenceSolver = new TypeInferenceSolver(lub, symbols, typeSubstitutionSolver);
T = getTypeVariable("T");
}
@Test
public void inferTypeSubstitution_always_return_first_type_match() {
TypeVariableJavaType U = getTypeVariable("U");
List<JavaType> formals = Lists.<JavaType>newArrayList(T, T, U);
List<JavaType> args = Lists.newArrayList(symbols.stringType, symbols.objectType, symbols.intType.primitiveWrapperType);
TypeSubstitution substitution = typeSubstitutionForTypeParameters(formals, args, T, U);
assertThat(substitution.substitutedType(T)).isSameAs(symbols.stringType);
assertThat(substitution.substitutedType(U)).isSameAs(symbols.intType.primitiveWrapperType);
args = Lists.newArrayList(symbols.objectType, symbols.stringType, symbols.intType.primitiveWrapperType);
substitution = typeSubstitutionForTypeParameters(formals, args, T, U);
assertThat(substitution.substitutedType(T)).isSameAs(symbols.objectType);
assertThat(substitution.substitutedType(U)).isSameAs(symbols.intType.primitiveWrapperType);
}
@Test
public void inferTypeSubstitution_missing_varargs() {
TypeVariableJavaType U = getTypeVariable("U");
List<JavaType> formals = Lists.<JavaType>newArrayList(T, new ArrayJavaType(U, symbols.arrayClass));
// 2nd parameter not provided, U is infered as Object
List<JavaType> args = Lists.newArrayList(symbols.stringType);
TypeSubstitution substitution = typeSubstitutionForTypeParametersWithVarargs(formals, args, T, U);
assertThat(substitution.substitutedType(T)).isSameAs(symbols.stringType);
assertThat(substitution.substitutedType(U)).isSameAs(symbols.objectType);
// 2nd parameter provided
args = Lists.newArrayList(symbols.stringType, symbols.intType.primitiveWrapperType);
substitution = typeSubstitutionForTypeParametersWithVarargs(formals, args, T, U);
assertThat(substitution.substitutedType(T)).isSameAs(symbols.stringType);
assertThat(substitution.substitutedType(U)).isSameAs(symbols.intType.primitiveWrapperType);
}
@Test
public void inferTypeSubstitution_varargs_and_generics() {
TypeVariableJavaType X = getTypeVariable("X");
JavaType aType = createType("A", symbols.objectType);
// A<{X=X}>
JavaType aXType = parametrizedTypeCache.getParametrizedTypeType(aType.symbol, new TypeSubstitution().add(X, X));
// A
JavaType aRawType = aXType.erasure();
// A<{X=? extends T}>
JavaType aWCextendsTType = parametrizedTypeCache.getParametrizedTypeType(aType.symbol, new TypeSubstitution().add(X, new WildCardType(T, BoundType.EXTENDS)));
// formals = A<{X=? extends T}>[]
List<JavaType> formals = Lists.<JavaType>newArrayList(new ArrayJavaType(aWCextendsTType, symbols.arrayClass));
// only raw types: args = A, A
List<JavaType> args = Lists.<JavaType>newArrayList(aRawType, aRawType);
TypeSubstitution substitution = typeSubstitutionForTypeParametersWithVarargs(formals, args, T);
assertThat(substitution.substitutedType(T)).isSameAs(symbols.objectType);
// raw type with generic type : A, A<String>
args = Lists.<JavaType>newArrayList(aRawType, parametrizedTypeCache.getParametrizedTypeType(aType.symbol, new TypeSubstitution().add(X, symbols.stringType)));
substitution = typeSubstitutionForTypeParametersWithVarargs(formals, args, T);
assertThat(substitution.substitutedType(T)).isSameAs(symbols.objectType);
}
@Test
public void inferTypeSubstitution_varargs() {
JavaType aType = createType("A", symbols.objectType);
// B <: A
JavaType bType = createType("B", aType);
// C <: A
JavaType cType = createType("C", aType);
// formals = T[] (varargs)
List<JavaType> formals = Lists.<JavaType>newArrayList(new ArrayJavaType(T, symbols.arrayClass));
// args = B, C
List<JavaType> args = Lists.<JavaType>newArrayList(bType, cType);
TypeSubstitution substitution = typeSubstitutionForTypeParametersWithVarargs(formals, args, T);
assertThat(substitution.substitutedType(T)).isSameAs(aType);
// args = int, long
args = Lists.<JavaType>newArrayList(symbols.intType, symbols.longType);
substitution = typeSubstitutionForTypeParametersWithVarargs(formals, args, T);
assertThat(substitution.substitutedType(T).is("java.lang.Number")).isTrue();
}
private JavaType createType(String string, JavaType superType) {
JavaSymbol.TypeJavaSymbol symbol = new JavaSymbol.TypeJavaSymbol(Flags.PUBLIC, "A", symbols.defaultPackage);
ClassJavaType type = (ClassJavaType) symbol.type;
type.interfaces = ImmutableList.of();
type.supertype = superType;
return type;
}
@Test
public void typeSubstitution_with_varargs_and_generics() {
Result result = Result.createForJavaFile("src/test/files/resolve/ParametrizedMethodAndVarargs");
JavaType childB = (JavaType) result.symbol("childB").type();
JavaType childC = (JavaType) result.symbol("childC").type();
JavaSymbol.MethodJavaSymbol variadicMethod;
List<JavaType> args;
TypeSubstitution typeSubstitution;
variadicMethod = (JavaSymbol.MethodJavaSymbol) result.symbol("bar");
args = Lists.newArrayList(childB, childB);
typeSubstitution = inferTypeSubstitution(variadicMethod, args);
assertThat(typeSubstitution.substitutedType(variadicMethod.typeVariableTypes.get(0)).is("B")).isTrue();
variadicMethod = (JavaSymbol.MethodJavaSymbol) result.symbol("foo");
args = Lists.newArrayList(childB, childC);
typeSubstitution = inferTypeSubstitution(variadicMethod, args);
assertThat(variadicMethod.usages()).hasSize(1);
assertThat(typeSubstitution.substitutedType(variadicMethod.typeVariableTypes.get(0)).is("A")).isTrue();
}
private TypeSubstitution inferTypeSubstitution(MethodJavaSymbol method, List<JavaType> args) {
return typeInferenceSolver.inferTypeSubstitution(method, toJavaTypes(method.parameterTypes()), args);
}
private List<JavaType> toJavaTypes(List<Type> types) {
List<JavaType> result = new ArrayList<>(types.size());
for (Type type : types) {
result.add((JavaType) type);
}
return result;
}
private TypeSubstitution typeSubstitutionForTypeParametersWithVarargs(List<JavaType> formals, List<JavaType> args, TypeVariableJavaType... typeParameters) {
return typeSubstitutionForTypeParameters(formals, args, true, typeParameters);
}
private TypeSubstitution typeSubstitutionForTypeParameters(List<JavaType> formals, List<JavaType> args, TypeVariableJavaType... typeParameters) {
return typeSubstitutionForTypeParameters(formals, args, false, typeParameters);
}
private TypeVariableJavaType getTypeVariable(String variableName) {
TypeVariableJavaType typeVariableJavaType = new TypeVariableJavaType(new JavaSymbol.TypeVariableJavaSymbol(variableName, Symbols.unknownSymbol));
typeVariableJavaType.bounds = ImmutableList.of(symbols.objectType);
return typeVariableJavaType;
}
private TypeSubstitution typeSubstitutionForTypeParameters(List<JavaType> formals, List<JavaType> args, boolean varargs, TypeVariableJavaType... typeParameters) {
MethodJavaType methodType = new MethodJavaType(formals, symbols.voidType, ImmutableList.<JavaType>of(), symbols.objectType.symbol);
int flags = Flags.PUBLIC;
if (varargs) {
flags |= Flags.VARARGS;
}
JavaSymbol.MethodJavaSymbol methodSymbol = new JavaSymbol.MethodJavaSymbol(flags, "foo", methodType, symbols.objectType.symbol);
for (TypeVariableJavaType typeParameter : typeParameters) {
methodSymbol.addTypeParameter(typeParameter);
}
return typeInferenceSolver.inferTypeSubstitution(methodSymbol, formals, args);
}
}