/* * 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.plugins.java.api.semantic.Type; import java.util.ArrayList; import java.util.List; import static org.assertj.core.api.Assertions.assertThat; public class TypeSubstitutionSolverTest { private Symbols symbols; private TypeSubstitutionSolver typeSubstitutionSolver; private ParametrizedTypeCache parametrizedTypeCache; private static TypeVariableJavaType T; @Before public void setUp() { parametrizedTypeCache = new ParametrizedTypeCache(); symbols = new Symbols(new BytecodeCompleter(Lists.<java.io.File>newArrayList(), parametrizedTypeCache)); typeSubstitutionSolver = new TypeSubstitutionSolver(parametrizedTypeCache, symbols); T = getTypeVariable("T"); } @Test public void applySubstitution_on_empty_list_of_parameters_has_no_effect() { List<JavaType> formals = new ArrayList<>(); TypeSubstitution substitution = new TypeSubstitution(); substitution.add(T, symbols.stringType); List<JavaType> substitutedFormals = typeSubstitutionSolver.applySubstitutionToFormalParameters(formals, substitution); assertThat(substitutedFormals).isEmpty(); assertThat(substitutedFormals).isSameAs(formals); } @Test public void applySubstitution_on_list_of_parameters_has_no_effect_with_empty_substitution() { List<JavaType> formals = Lists.newArrayList(symbols.stringType); List<JavaType> substitutedFormals = typeSubstitutionSolver.applySubstitutionToFormalParameters(formals, new TypeSubstitution()); assertThat(substitutedFormals).isSameAs(formals); } @Test public void applySubstitution_on_simple_types() { List<JavaType> formals = Lists.newArrayList((JavaType) T); TypeSubstitution substitution = new TypeSubstitution(); substitution.add(T, symbols.stringType); List<JavaType> substitutedFormals = typeSubstitutionSolver.applySubstitutionToFormalParameters(formals, substitution); assertThat(substitutedFormals).hasSize(formals.size()); assertThat(substitutedFormals).containsExactly(symbols.stringType); } @Test public void applySubstitution_on_array_with_generic() { TypeVariableJavaType X = getTypeVariable("X"); JavaSymbol.TypeJavaSymbol aSymbol = new JavaSymbol.TypeJavaSymbol(Flags.PUBLIC, "A", symbols.defaultPackage); // A<{X=T}> JavaType aXT = parametrizedTypeCache.getParametrizedTypeType(aSymbol, new TypeSubstitution().add(X, T)); // A<T>[] JavaType formal1 = new ArrayJavaType(aXT, symbols.arrayClass); // A<T>[][] JavaType formal2 = new ArrayJavaType(new ArrayJavaType(aXT, symbols.arrayClass), symbols.arrayClass); List<JavaType> substitutedTypes = typeSubstitutionSolver.applySubstitutionToFormalParameters(ImmutableList.of(formal1, formal2), new TypeSubstitution().add(T, symbols.stringType)); JavaType substituted1 = substitutedTypes.get(0); assertThat(substituted1).isInstanceOf(ArrayJavaType.class); JavaType elementType = ((ArrayJavaType) substituted1).elementType; assertThat(elementType).isInstanceOf(ParametrizedTypeJavaType.class); ParametrizedTypeJavaType ptt = (ParametrizedTypeJavaType) elementType; assertThat(ptt.substitution(X)).isSameAs(symbols.stringType); JavaType substituted2 = substitutedTypes.get(1); assertThat(substituted2).isInstanceOf(ArrayJavaType.class); elementType = ((ArrayJavaType) ((ArrayJavaType) substituted2).elementType).elementType; assertThat(elementType).isInstanceOf(ParametrizedTypeJavaType.class); ptt = (ParametrizedTypeJavaType) elementType; assertThat(ptt.substitution(X)).isSameAs(symbols.stringType); } @Test public void applySubstitution_on_nested_parametrized_types() { TypeVariableJavaType k = getTypeVariable("K"); TypeSubstitution aSubs = new TypeSubstitution(); aSubs.add(k, T); JavaSymbol.TypeJavaSymbol aSymbol = new JavaSymbol.TypeJavaSymbol(Flags.PUBLIC, "A", Symbols.rootPackage); // A<{K=T}> ParametrizedTypeJavaType aRoot = new ParametrizedTypeJavaType(aSymbol, aSubs); // A<...n-1...<A<T>>...> JavaType last = aRoot; int n = 10; for (int i = 0; i < n; i++) { TypeSubstitution newSubs = new TypeSubstitution(); newSubs.add(k, last); last = new ParametrizedTypeJavaType(aSymbol, newSubs); } List<JavaType> formals = Lists.newArrayList(last); TypeSubstitution substitution = new TypeSubstitution(); substitution.add(T, symbols.stringType); List<JavaType> substitutedFormals = typeSubstitutionSolver.applySubstitutionToFormalParameters(formals, substitution); JavaType type = substitutedFormals.get(0); int nbNestedGenerics = 0; while (type instanceof ParametrizedTypeJavaType) { type = ((ParametrizedTypeJavaType) type).substitution(k); nbNestedGenerics++; } assertThat(nbNestedGenerics).isEqualTo(n + 1); assertThat(type).isEqualTo(symbols.stringType); } @Test public void getSubstitutionFromTypeParams_does_not_provide_substitution_if_arity_of_params_is_not_matching() { ArrayList<TypeVariableJavaType> typeVariableTypes = Lists.newArrayList(T, T); ArrayList<JavaType> typeParams = Lists.newArrayList(symbols.stringType); TypeSubstitution substitution = typeSubstitutionSolver.getSubstitutionFromTypeParams(typeVariableTypes, typeParams); assertThat(substitution.size()).isEqualTo(0); } @Test public void getSubstitutionFromTypeParams_provide_substitution() { TypeVariableJavaType j = getTypeVariable("J"); TypeVariableJavaType k = getTypeVariable("K"); ArrayList<TypeVariableJavaType> typeVariableTypes = Lists.newArrayList(j, k); ArrayList<JavaType> typeParams = Lists.newArrayList(symbols.stringType, symbols.intType.primitiveWrapperType); TypeSubstitution substitution = typeSubstitutionSolver.getSubstitutionFromTypeParams(typeVariableTypes, typeParams); assertThat(substitution.size()).isEqualTo(2); assertThat(substitution.substitutedType(j)).isSameAs(symbols.stringType); assertThat(substitution.substitutedType(k)).isSameAs(symbols.intType.primitiveWrapperType); } private TypeVariableJavaType getTypeVariable(String variableName) { TypeVariableJavaType typeVariableJavaType = new TypeVariableJavaType(new JavaSymbol.TypeVariableJavaSymbol(variableName, Symbols.unknownSymbol)); typeVariableJavaType.bounds = ImmutableList.of(symbols.objectType); return typeVariableJavaType; } @Test public void substitutionFromSuperType_from_same_type() { Result result = Result.createForJavaFile("src/test/files/sym/TypeSubstitutionSolver"); JavaType stringType = (JavaType) result.symbol("string").type(); ParametrizedTypeJavaType aString = (ParametrizedTypeJavaType) result.symbol("aString").type(); ParametrizedTypeJavaType aX = (ParametrizedTypeJavaType) result.symbol("aX").type(); TypeVariableJavaType x = aX.typeParameters().get(0); TypeSubstitution substitution = TypeSubstitutionSolver.substitutionFromSuperType(aX, aString); assertThat(substitution.size()).isEqualTo(1); assertThat(substitution.typeVariables()).containsExactly(x); assertThat(substitution.substitutedTypes()).containsExactly(stringType); } @Test public void substitutionFromSuperType_direct_inheritance() { Result result = Result.createForJavaFile("src/test/files/sym/TypeSubstitutionSolver"); JavaType stringType = (JavaType) result.symbol("string").type(); ParametrizedTypeJavaType iString = (ParametrizedTypeJavaType) result.symbol("iString").type(); ParametrizedTypeJavaType aX = (ParametrizedTypeJavaType) result.symbol("aX").type(); TypeVariableJavaType x = aX.typeParameters().get(0); TypeSubstitution substitution = TypeSubstitutionSolver.substitutionFromSuperType(aX, iString); assertThat(substitution.size()).isEqualTo(1); assertThat(substitution.typeVariables()).containsExactly(x); assertThat(substitution.substitutedTypes()).containsExactly(stringType); } @Test public void substitutionFromSuperType_concrete_type() { Result result = Result.createForJavaFile("src/test/files/sym/TypeSubstitutionSolver"); ParametrizedTypeJavaType lNumber = (ParametrizedTypeJavaType) result.symbol("lNumber").type(); ParametrizedTypeJavaType aX = (ParametrizedTypeJavaType) result.symbol("aX").type(); TypeVariableJavaType x = aX.typeParameters().get(0); TypeSubstitution substitution = TypeSubstitutionSolver.substitutionFromSuperType(aX, lNumber); assertThat(substitution.size()).isEqualTo(1); assertThat(substitution.typeVariables()).containsExactly(x); assertThat(substitution.substitutedTypes()).containsExactly(x); } @Test public void substitutionFromSuperType_from_non_related_types_does_nothing() { Result result = Result.createForJavaFile("src/test/files/sym/TypeSubstitutionSolver"); ParametrizedTypeJavaType jStringInteger = (ParametrizedTypeJavaType) result.symbol("jStringInteger").type(); ParametrizedTypeJavaType bXY = (ParametrizedTypeJavaType) result.symbol("bXY").type(); TypeVariableJavaType x = bXY.typeParameters().get(0); TypeVariableJavaType y = bXY.typeParameters().get(1); TypeSubstitution substitution = TypeSubstitutionSolver.substitutionFromSuperType(bXY, jStringInteger); assertThat(substitution.size()).isEqualTo(2); assertThat(substitution.typeVariables()).containsExactly(x, y); assertThat(substitution.substitutedTypes()).containsExactly(x, y); } @Test public void substitutionFromSuperType_with_multiple_variables() { Result result = Result.createForJavaFile("src/test/files/sym/TypeSubstitutionSolver"); Type stringType = result.symbol("string").type(); Type integerType = result.symbol("integer").type(); ParametrizedTypeJavaType jStringInteger = (ParametrizedTypeJavaType) result.symbol("jStringInteger").type(); ParametrizedTypeJavaType cXY = (ParametrizedTypeJavaType) result.symbol("cXY").type(); TypeVariableJavaType x = cXY.typeParameters().get(0); TypeVariableJavaType y = cXY.typeParameters().get(1); TypeSubstitution substitution = TypeSubstitutionSolver.substitutionFromSuperType(cXY, jStringInteger); assertThat(substitution.size()).isEqualTo(2); assertThat(substitution.typeVariables()).containsExactly(x, y); assertThat(substitution.substitutedType(x)).isSameAs(stringType); assertThat(substitution.substitutedType(y)).isSameAs(integerType); } @Test public void substitutionFromSuperType_with_2_level_inheritance() { Result result = Result.createForJavaFile("src/test/files/sym/TypeSubstitutionSolver"); JavaType stringType = (JavaType) result.symbol("string").type(); ParametrizedTypeJavaType iString = (ParametrizedTypeJavaType) result.symbol("iString").type(); ParametrizedTypeJavaType dV = (ParametrizedTypeJavaType) result.symbol("dX").type(); TypeVariableJavaType v = dV.typeParameters().get(0); TypeSubstitution substitution = TypeSubstitutionSolver.substitutionFromSuperType(dV, iString); assertThat(substitution.size()).isEqualTo(1); assertThat(substitution.typeVariables()).containsExactly(v); assertThat(substitution.substitutedTypes()).containsExactly(stringType); } @Test public void substitutionFromSuperType_complex_inheritance_and_multiple_variables() { Result result = Result.createForJavaFile("src/test/files/sym/TypeSubstitutionSolver"); Type stringType = result.symbol("string").type(); Type integerType = result.symbol("integer").type(); ParametrizedTypeJavaType jStringInteger = (ParametrizedTypeJavaType) result.symbol("jStringInteger").type(); ParametrizedTypeJavaType fWXYZ = (ParametrizedTypeJavaType) result.symbol("fWXYZ").type(); TypeVariableJavaType w = fWXYZ.typeParameters().get(0); TypeVariableJavaType x = fWXYZ.typeParameters().get(1); TypeVariableJavaType y = fWXYZ.typeParameters().get(2); TypeVariableJavaType z = fWXYZ.typeParameters().get(3); TypeSubstitution substitution = TypeSubstitutionSolver.substitutionFromSuperType(fWXYZ, jStringInteger); assertThat(substitution.size()).isEqualTo(4); assertThat(substitution.typeVariables()).containsExactly(w, x, y, z); assertThat(substitution.substitutedType(w)).isSameAs(stringType); assertThat(substitution.substitutedType(x)).isSameAs(x); assertThat(substitution.substitutedType(y)).isSameAs(y); assertThat(substitution.substitutedType(z)).isSameAs(integerType); } @Test public void test_no_infinite_recursion_on_validation() throws Exception { Result result = Result.createForJavaFile("src/test/files/sym/TypeSubstitutionSolver"); JavaType inst = result.symbol("inst").type; assertThat(inst.isParameterized()).isTrue(); List<TypeVariableJavaType> typeVariableJavaTypes = ((ParametrizedTypeJavaType) inst).typeParameters(); assertThat(typeVariableJavaTypes).hasSize(2); assertThat(((ParametrizedTypeJavaType) inst).substitution(typeVariableJavaTypes.get(0)).is("java.lang.Object")).isTrue(); assertThat(((ParametrizedTypeJavaType) inst).substitution(typeVariableJavaTypes.get(1)).is("java.lang.Boolean")).isTrue(); } @Test public void inference_on_parameters_supertypes() throws Exception { Result result = Result.createForJavaFile("src/test/files/sym/TypeInferenceOnSupertypes"); assertThat(result.reference(6, 5)).isSameAs(result.symbol("foo")); Type type = result.referenceTree(6, 5).symbolType(); assertThat(((MethodJavaType) type).resultType.is("java.lang.String")).isTrue(); } }