/* * 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.assertj.core.api.AbstractObjectAssert; import org.junit.Before; import org.junit.Test; import org.sonar.java.ast.parser.JavaParser; import org.sonar.java.model.declaration.ClassTreeImpl; import org.sonar.java.model.declaration.MethodTreeImpl; import org.sonar.java.model.declaration.VariableTreeImpl; import org.sonar.plugins.java.api.semantic.SymbolMetadata.AnnotationInstance; import org.sonar.plugins.java.api.semantic.Type; import org.sonar.plugins.java.api.tree.BaseTreeVisitor; import org.sonar.plugins.java.api.tree.ClassTree; import org.sonar.plugins.java.api.tree.CompilationUnitTree; import org.sonar.plugins.java.api.tree.ExpressionStatementTree; import org.sonar.plugins.java.api.tree.ExpressionTree; import org.sonar.plugins.java.api.tree.MethodTree; import org.sonar.plugins.java.api.tree.Tree; import org.sonar.plugins.java.api.tree.VariableTree; import java.io.File; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.List; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Matchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; public class TypeAndReferenceSolverTest { private final ParametrizedTypeCache parametrizedTypeCache = new ParametrizedTypeCache(); private final BytecodeCompleter bytecodeCompleter = new BytecodeCompleter(Lists.newArrayList(new File("target/test-classes"), new File("target/classes")), parametrizedTypeCache); private final Symbols symbols = new Symbols(bytecodeCompleter); private Resolve.Env env; private JavaSymbol.TypeJavaSymbol classSymbol; private ClassJavaType classType; private JavaSymbol variableSymbol; private JavaSymbol methodSymbol; private JavaSymbol argMethodSymbol; /** * Simulates creation of symbols and types. */ @Before public void setUp() { JavaSymbol.PackageJavaSymbol p = symbols.defaultPackage; p.members = new Scope(p); // class MyClass classSymbol = new JavaSymbol.TypeJavaSymbol(0, "MyClass", p); classType = (ClassJavaType) classSymbol.type; classType.supertype = symbols.objectType; classType.interfaces = ImmutableList.of(); classSymbol.members = new Scope(classSymbol); p.members.enter(classSymbol); // int[][] var; variableSymbol = new JavaSymbol.VariableJavaSymbol(0, "var", classSymbol); variableSymbol.type = new ArrayJavaType(new ArrayJavaType(symbols.intType, symbols.arrayClass), symbols.arrayClass); classSymbol.members.enter(variableSymbol); // MyClass var2; classSymbol.members.enter(new JavaSymbol.VariableJavaSymbol(0, "var2", classType, classSymbol)); // int method() methodSymbol = new JavaSymbol.MethodJavaSymbol(0, "method", classSymbol); ((JavaSymbol.MethodJavaSymbol)methodSymbol).setMethodType(new MethodJavaType(ImmutableList.<JavaType>of(), symbols.intType, ImmutableList.<JavaType>of(), classSymbol)); classSymbol.members.enter(methodSymbol); // int method() argMethodSymbol = new JavaSymbol.MethodJavaSymbol(0, "argMethod", classSymbol); ((JavaSymbol.MethodJavaSymbol)argMethodSymbol).setMethodType(new MethodJavaType(ImmutableList.of(symbols.intType), symbols.intType, ImmutableList.<JavaType>of(), classSymbol)); classSymbol.members.enter(argMethodSymbol); classSymbol.members.enter(new JavaSymbol.VariableJavaSymbol(0, "this", classType, classSymbol)); classSymbol.members.enter(new JavaSymbol.VariableJavaSymbol(0, "super", classType.supertype, classSymbol)); JavaSymbol.MethodJavaSymbol defaultConstructor = new JavaSymbol.MethodJavaSymbol(classSymbol.flags & Flags.ACCESS_FLAGS, "<init>", classSymbol); MethodJavaType defaultConstructorType = new MethodJavaType(ImmutableList.<JavaType>of(), null, ImmutableList.<JavaType>of(), classSymbol); defaultConstructor.setMethodType(defaultConstructorType); classSymbol.members.enter(defaultConstructor); // FIXME figure out why top is mandatory Resolve.Env top = new Resolve.Env(); top.scope = new Scope((JavaSymbol) null); Resolve.Env compilationUnitEnv = new Resolve.Env(); compilationUnitEnv.outer = top; compilationUnitEnv.packge = p; compilationUnitEnv.scope = p.members; compilationUnitEnv.enclosingClass = symbols.predefClass; compilationUnitEnv.namedImports = new Scope(p); compilationUnitEnv.starImports = new Scope(p); compilationUnitEnv.staticStarImports = new Scope(p); env = compilationUnitEnv.dup(); env.outer = compilationUnitEnv; env.enclosingClass = classSymbol; env.scope = classSymbol.members; } @Test public void SOE_on_binary_expression() throws Exception { String code = "class A { String foo() { return "; for (int i = 0; i < 2000; i++) { code += "\"i\"+"; } code+="i; }}"; try { treeOf(code); } catch (StackOverflowError soe) { throw new AssertionError("Stackoverflow error was thrown !"); } } @Test public void annotation_on_method() { CompilationUnitTree compilationUnit = treeOf("@interface MyAnnotation { } class Class { @MyAnnotation void method() { } }"); ClassTreeImpl annotation = (ClassTreeImpl) compilationUnit.types().get(0); ClassTreeImpl clazz = (ClassTreeImpl) compilationUnit.types().get(1); MethodTreeImpl method = (MethodTreeImpl) clazz.members().get(0); List<AnnotationInstance> annotations = ((JavaSymbol.MethodJavaSymbol) method.symbol()).metadata().annotations(); assertThat(annotations.size()).isEqualTo(1); assertThat(annotations.get(0).symbol().type().is(annotation.symbol().name())).isTrue(); } @Test public void annotation_on_method_parameter() { CompilationUnitTree compilationUnit = treeOf("@interface MyAnnotation { } class Class { void method(@MyAnnotation int a) { } }"); ClassTreeImpl annotation = (ClassTreeImpl) compilationUnit.types().get(0); ClassTreeImpl clazz = (ClassTreeImpl) compilationUnit.types().get(1); MethodTreeImpl method = (MethodTreeImpl) clazz.members().get(0); VariableTreeImpl parameter = (VariableTreeImpl)method.parameters().get(0); List<AnnotationInstance> annotations = parameter.getSymbol().metadata().annotations(); assertThat(annotations.size()).isEqualTo(1); assertThat(annotations.get(0).symbol().type().is(annotation.symbol().name())).isTrue(); } @Test public void annotation_on_type() { CompilationUnitTree compilationUnit = treeOf("@interface MyAnnotation { } @MyAnnotation class Class { }"); ClassTreeImpl annotation = (ClassTreeImpl) compilationUnit.types().get(0); ClassTreeImpl clazz = (ClassTreeImpl) compilationUnit.types().get(1); List<AnnotationInstance> annotations = ((JavaSymbol.TypeJavaSymbol) clazz.symbol()).metadata().annotations(); assertThat(annotations.size()).isEqualTo(1); assertThat(annotations.get(0).symbol().type().is(annotation.symbol().name())).isTrue(); } @Test public void annotation_on_variable() { CompilationUnitTree compilationUnit = treeOf("@interface MyAnnotation { } class Class { @MyAnnotation Object field; }"); ClassTreeImpl annotation = (ClassTreeImpl) compilationUnit.types().get(0); ClassTreeImpl clazz = (ClassTreeImpl) compilationUnit.types().get(1); VariableTreeImpl variable = (VariableTreeImpl) clazz.members().get(0); List<AnnotationInstance> annotations = variable.getSymbol().metadata().annotations(); assertThat(annotations.size()).isEqualTo(1); assertThat(annotations.get(0).symbol().type().is(annotation.symbol().name())).isTrue(); } @Test public void annotation_completion() { AnnotationInstance annotation1 = extractFirstAnnotationInstance("@interface MyAnnotation { } @MyAnnotation() class Class { }"); assertThat(annotation1.values()).isEmpty(); AnnotationInstance annotation2 = extractFirstAnnotationInstance("@interface MyAnnotation { } @MyAnnotation(expr) class Class { }"); assertThat(annotation2.values().size()).isEqualTo(1); assertThat(annotation2.values().get(0).name()).isEqualTo(""); AnnotationInstance annotation3 = extractFirstAnnotationInstance("@interface MyAnnotation { public static final String field; String value(); } @MyAnnotation(expr) class Class { }"); assertThat(annotation3.values().size()).isEqualTo(1); assertThat(annotation3.values().get(0).name()).isEqualTo("value"); AnnotationInstance annotation4 = extractFirstAnnotationInstance("@interface MyAnnotation { } @MyAnnotation(expr = val) class Class { }"); assertThat(annotation4.values().size()).isEqualTo(1); assertThat(annotation4.values().get(0).name()).isEqualTo("expr"); AnnotationInstance annotation5 = extractFirstAnnotationInstance("@interface MyAnnotation { } @MyAnnotation(expr1 = val1, expr2 = val2) class Class { }"); assertThat(annotation5.values().size()).isEqualTo(2); assertThat(annotation5.values().get(0).name()).isEqualTo("expr1"); assertThat(annotation5.values().get(1).name()).isEqualTo("expr2"); } private AnnotationInstance extractFirstAnnotationInstance(String source) { ClassTree tree = (ClassTree) treeOf(source).types().get(1); return ((JavaSymbol.TypeJavaSymbol) tree.symbol()).metadata().annotations().get(0); } @Test public void primary_literal() { assertThat(typeOf("false")).isSameAs(symbols.booleanType); assertThat(typeOf("true")).isSameAs(symbols.booleanType); assertThat(typeOf("null")).isSameAs(symbols.nullType); assertThat(typeOf("'a'")).isSameAs(symbols.charType); assertThat(typeOf("\"foo\"")).isSameAs(symbols.stringType); assertThat(typeOf("42F")).isSameAs(symbols.floatType); assertThat(typeOf("42D")).isSameAs(symbols.doubleType); assertThat(typeOf("42L")).isSameAs(symbols.longType); assertThat(typeOf("42")).isSameAs(symbols.intType); } @Test public void primary_this() { assertThat(typeOfExpression("this")).isSameAs(classType); // constructor call assertThat(typeOf("this(arguments)")).isSameAs(symbols.unknownType); } @Test public void primary_super() { // constructor call assertThat(typeOf("super(arguments)")).isSameAs(symbols.unknownType); // method call assertThat(typeOf("super.method(1)")).isSameAs(symbols.unknownType); // field access assertThat(typeOfExpression("super.clone()")).isSameAs(classType.supertype); } @Test public void primary_par_expression() { // (expression) assertThat(typeOf("((int) 42L)")).isSameAs(symbols.intType); } @Test public void primary_new() { assertThat(typeOf("new MyClass()")).isSameAs(classType); assertThat(typeOf("new MyClass() {}")).isSameAs(symbols.unknownType); // TODO proper implementation of this test requires definition of equality for types assertThat(typeOf("new MyClass[]{}")).isInstanceOf(ArrayJavaType.class); assertThat(typeOf("new int[]{}")).isInstanceOf(ArrayJavaType.class); assertThat(typeOf("new int[][]{}")).isInstanceOf(ArrayJavaType.class); } @Test public void primary_qualified_identifier() { // qualified_identifier assertThat(typeOfExpression("var")).isSameAs(variableSymbol.type); assertThat(typeOfExpression("var.length")).isSameAs(symbols.intType); assertThat(typeOfExpression("MyClass.var")).isSameAs(variableSymbol.type); // qualified_identifier[expression] assertThat(typeOf("var[42] = 12")).isSameAs(((ArrayJavaType) variableSymbol.type).elementType); assertThat(typeOfExpression("var[42]")).isSameAs(((ArrayJavaType) variableSymbol.type).elementType); // qualified_identifier[].class assertThat(typeOfExpression("id[].class").erasure()).isSameAs(symbols.classType); assertThat(typeOfExpression("id[][].class").erasure()).isSameAs(symbols.classType); // qualified_identifier(arguments) assertThat(typeOf("argMethod(1)")).isSameAs(symbols.intType); assertThat(typeOf("var2.method()")).isSameAs(symbols.intType); assertThat(typeOf("MyClass.var2.method()")).isSameAs(symbols.intType); // qualified_identifier.class JavaType idClassType = typeOfExpression("id.class"); assertThat(idClassType.erasure()).isSameAs(symbols.classType); assertThat(idClassType).isInstanceOf(ParametrizedTypeJavaType.class); ParametrizedTypeJavaType idClassTypeParameterized = (ParametrizedTypeJavaType) idClassType; assertThat(idClassTypeParameterized.typeSubstitution.substitutedType(idClassTypeParameterized.typeParameters().get(0))).isSameAs(Symbols.unknownType); // TODO id.<...>... assertThat(typeOfExpression("MyClass.this")).isSameAs(classSymbol.type); assertThat(typeOf("id.super(arguments)")).isSameAs(symbols.unknownType); // TODO id.new... } @Test public void primary_basic_type() { JavaType intClassType = typeOfExpression("int.class"); assertThat(intClassType.erasure()).isSameAs(symbols.classType); assertThat(intClassType).isInstanceOf(ParametrizedTypeJavaType.class); ParametrizedTypeJavaType intClassTypeParameterized = (ParametrizedTypeJavaType) intClassType; assertThat(intClassTypeParameterized.typeSubstitution.substitutedType(intClassTypeParameterized.typeParameters().get(0))).isSameAs(symbols.intType.primitiveWrapperType()); JavaType stringClassType = typeOfExpression("java.lang.String.class"); assertThat(stringClassType.erasure()).isSameAs(symbols.classType); assertThat(stringClassType).isInstanceOf(ParametrizedTypeJavaType.class); ParametrizedTypeJavaType StringClassTypeParameterized = (ParametrizedTypeJavaType) stringClassType; assertThat(StringClassTypeParameterized.typeSubstitution.substitutedType(StringClassTypeParameterized.typeParameters().get(0))).isSameAs(symbols.stringType); assertThat(typeOfExpression("int[].class").erasure()).isSameAs(symbols.classType); assertThat(typeOfExpression("int[][].class").erasure()).isSameAs(symbols.classType); } @Test public void primary_void() { assertThat(typeOfExpression("void.class").erasure()).isSameAs(symbols.classType); } @Test public void type_cast() { // (basic_type) expression assertThat(typeOf("(byte) 42L")).isSameAs(symbols.byteType); assertThat(typeOf("(char) 42")).isSameAs(symbols.charType); assertThat(typeOf("(short) 42L")).isSameAs(symbols.shortType); assertThat(typeOf("(int) 42")).isSameAs(symbols.intType); assertThat(typeOf("(long) 42")).isSameAs(symbols.longType); assertThat(typeOf("(float) 42")).isSameAs(symbols.floatType); assertThat(typeOf("(double) 42")).isSameAs(symbols.doubleType); assertThat(typeOf("(boolean) true")).isSameAs(symbols.booleanType); // (class_type) expression assertThat(typeOf("(MyClass) 42")).isSameAs(classSymbol.type); } @Test public void prefix_op() { for (String op : Arrays.asList("++", "--", "!", "~", "+", "-")) { assertThat(typeOf(op + INT)).as(op + INT).isSameAs(symbols.intType); } } @Test public void postfix_op() { for (String op : Arrays.asList("++", "--")) { assertThat(typeOf(INT + op)).as(INT + op).isSameAs(symbols.intType); } } @Test public void selector() { // method call assertThat(typeOf("this.method()").isTagged(JavaType.INT)).isTrue(); assertThat(typeOf("this.argMethod(12)").isTagged(JavaType.INT)).isTrue(); assertThat(typeOf("var[42].clone()")).isSameAs(symbols.objectType); // field access assertThat(typeOfExpression("this.var")).isSameAs(variableSymbol.type); assertThat(typeOfExpression("var[42].length")).isSameAs(symbols.intType); // array access assertThat(typeOfExpression("var[42][42]")).isSameAs(((ArrayJavaType) ((ArrayJavaType) variableSymbol.type).elementType).elementType); } @Test public void multiplicative_and_additive_expression() { for (String op : Arrays.asList("*", "/", "%", "+", "-")) { for (String o1 : Arrays.asList(CHAR, BYTE, SHORT, INT)) { for (String o2 : Arrays.asList(CHAR, BYTE, SHORT, INT)) { assertThatTypeOf(o1, op, o2).isSameAs(symbols.intType); } } for (String other : Arrays.asList(CHAR, BYTE, SHORT, INT, LONG)) { assertThatTypeOf(LONG, op, other).isSameAs(symbols.longType); assertThatTypeOf(other, op, LONG).isSameAs(symbols.longType); } for (String other : Arrays.asList(CHAR, BYTE, SHORT, INT, LONG, FLOAT)) { assertThatTypeOf(FLOAT, op, other).isSameAs(symbols.floatType); assertThatTypeOf(other, op, FLOAT).isSameAs(symbols.floatType); } for (String other : Arrays.asList(CHAR, BYTE, SHORT, INT, LONG, FLOAT, DOUBLE)) { assertThatTypeOf(DOUBLE, op, other).isSameAs(symbols.doubleType); assertThatTypeOf(other, op, DOUBLE).isSameAs(symbols.doubleType); } } // TODO // string, object = string // object, string = string for (String other : Arrays.asList(STRING, CHAR, BYTE, SHORT, INT, LONG, FLOAT, DOUBLE, BOOLEAN, NULL)) { assertThatTypeOf(STRING, "+", other).isSameAs(symbols.stringType); assertThatTypeOf(other, "+", STRING).isSameAs(symbols.stringType); } // TODO check that null + null won't produce string - see Javac } @Test public void shift_expression() { for (String op : Arrays.asList("<<", ">>", ">>>")) { assertThatTypeOf(INT, op, INT).isSameAs(symbols.intType); assertThatTypeOf(INT, op, LONG).isSameAs(symbols.intType); assertThatTypeOf(LONG, op, LONG).isSameAs(symbols.longType); assertThatTypeOf(LONG, op, INT).isSameAs(symbols.longType); } } @Test public void relational_expression() { for (String op : Arrays.asList("<", ">", ">=", "<=")) { for (String o1 : Arrays.asList(CHAR, BYTE, SHORT, INT, LONG, FLOAT, DOUBLE)) { for (String o2 : Arrays.asList(CHAR, BYTE, SHORT, INT, LONG, FLOAT, DOUBLE)) { assertThatTypeOf(o1, op, o2).isSameAs(symbols.booleanType); } } } assertThat(typeOf("var instanceof Object")).isSameAs(symbols.booleanType); } @Test public void equality_expression() { // TODO object, object = boolean for (String op : Arrays.asList("==", "!=")) { for (String o1 : Arrays.asList(CHAR, BYTE, SHORT, INT, LONG, FLOAT, DOUBLE)) { for (String o2 : Arrays.asList(CHAR, BYTE, SHORT, INT, LONG, FLOAT, DOUBLE)) { assertThatTypeOf(o1, op, o2).isSameAs(symbols.booleanType); } } assertThatTypeOf(BOOLEAN, op, BOOLEAN).isSameAs(symbols.booleanType); } } @Test public void and_expression() { assertThatTypeOf(BOOLEAN, "&", BOOLEAN).isSameAs(symbols.booleanType); for (String o1 : Arrays.asList(CHAR, BYTE, SHORT, INT)) { for (String o2 : Arrays.asList(CHAR, BYTE, SHORT, INT)) { assertThatTypeOf(o1, "&", o2).isSameAs(symbols.intType); } } assertThatTypeOf(LONG, "&", LONG).isSameAs(symbols.longType); } @Test public void exclusive_or_expression() { assertThatTypeOf(BOOLEAN, "^", BOOLEAN).isSameAs(symbols.booleanType); for (String o1 : Arrays.asList(CHAR, BYTE, SHORT, INT)) { for (String o2 : Arrays.asList(CHAR, BYTE, SHORT, INT)) { assertThatTypeOf(o1, "^", o2).isSameAs(symbols.intType); } } assertThatTypeOf(LONG, "^", LONG).isSameAs(symbols.longType); } @Test public void inclusive_or_expression() { assertThatTypeOf(BOOLEAN, "|", BOOLEAN).isSameAs(symbols.booleanType); for (String o1 : Arrays.asList(CHAR, BYTE, SHORT, INT)) { for (String o2 : Arrays.asList(CHAR, BYTE, SHORT, INT)) { assertThatTypeOf(o1, "|", o2).isSameAs(symbols.intType); } } assertThatTypeOf(LONG, "|", LONG).isSameAs(symbols.longType); } @Test public void conditional_and_expression() { assertThatTypeOf(BOOLEAN, "&&", BOOLEAN).isSameAs(symbols.booleanType); } @Test public void conditional_or_expression() { assertThatTypeOf(BOOLEAN, "||", BOOLEAN).isSameAs(symbols.booleanType); } private AbstractObjectAssert<?, JavaType> assertThatTypeOf(String o1, String op, String o2) { return assertThat(typeOf(o1 + op + o2)).as(o1 + op + o2); } @Test public void conditional_expression() { assertThat(typeOf("true ? 42 : 42")).isSameAs(symbols.intType); assertThat(typeOf("true ? null : 42")).isSameAs(symbols.intType.primitiveWrapperType()); assertThat(typeOf("true ? 42.0 : null")).isSameAs(symbols.doubleType.primitiveWrapperType()); assertThat(typeOf("true ? new Long(2) : new Long(2)")).isSameAs(symbols.longType.primitiveWrapperType()); assertThat(typeOf("true ? new MyClass() : new MyClass()")).isSameAs(classType); assertThat(typeOf("true ? 1 : 3.0")).isSameAs(symbols.doubleType); assertThat(typeOf("true ? new Long(2) : (short) 0")).isSameAs(symbols.longType); assertThat(typeOf("true ? var[0][2] : (short) 0")).isSameAs(symbols.shortType); // should be int because var[0][2] is not a constant but we approximate to narrowest type assertThat(typeOf("true ? (short) 0 : var[0][2]")).isSameAs(symbols.shortType); // should be int because var[0][2] is not a constant but we approximate to narrowest type assertThat(typeOf("true ? 1 : (short) 0")).isSameAs(symbols.shortType); assertThat(typeOf("true ? (short) 0 : 1")).isSameAs(symbols.shortType); assertThat(typeOf("true ? null : new Integer(0)")).isSameAs(symbols.intType.primitiveWrapperType()); assertThat(typeOf("true ? null : new MyClass()")).isSameAs(classType); assertThat(typeOf("true ? new MyClass() : null")).isSameAs(classType); assertThat(typeOf("true ? new MyClass() : new Object()")).isSameAs(symbols.objectType); assertThat(typeOf("true ? new Object() : new MyClass()")).isSameAs(symbols.objectType); ParametrizedTypeJavaType expectedType = (ParametrizedTypeJavaType) typeOf("new java.util.ArrayList<? extends String>()"); JavaType testedType = typeOf("true ? new java.util.ArrayList<? extends String>() : new java.util.ArrayList<String>()"); assertThat(testedType).isInstanceOf(ParametrizedTypeJavaType.class); assertThat(((ParametrizedTypeJavaType) testedType).typeSubstitution).isEqualTo(expectedType.typeSubstitution); testedType = typeOf("true ? new java.util.ArrayList<Integer>() : new java.util.ArrayList<String>()"); assertThat(testedType).isInstanceOf(ParametrizedTypeJavaType.class); assertThat(testedType.is("java.util.ArrayList")).isTrue(); testedType = typeOf("true ? ((Iterable<Class<? extends String>>) new java.util.ArrayList<Class<? extends String>>()) : new java.util.ArrayList<Class<? extends String>>()"); assertThat(testedType).isInstanceOf(ParametrizedTypeJavaType.class); assertThat(testedType.is("java.lang.Iterable")).isTrue(); } @Test public void lambda_expression() { assertThat(typeOf("a -> a+1").isTagged(JavaType.DEFERRED)).isTrue(); } @Test public void assignment_expression() { assertThat(typeOf("var = 1")).isSameAs(variableSymbol.type); } @Test public void parametrized_method_return_type_is_array() { Type arrayType = returnTypeOf("<T> T[] foo(T t) { return null; }", "this.<String>foo(\"hello\");"); assertThat(arrayType.isArray()).isTrue(); assertThat(((ArrayJavaType) arrayType).elementType.is("java.lang.String")).isTrue(); } @Test public void parametrized_method_return_type_is_multidimensionnal_array() { Type arrayType = returnTypeOf("<T> T[][][][][] foo(T t) { return null; }", "this.<String>foo(\"hello\");"); for (int i = 0; i < 4; i++) { assertThat(arrayType.isArray()).isTrue(); arrayType = ((Type.ArrayType) arrayType).elementType(); } assertThat(((ArrayJavaType) arrayType).elementType.is("java.lang.String")).isTrue(); } private Type returnTypeOf(String method, String invocation) { CompilationUnitTree compilationUnit = treeOf("class A { " + method + " void test() { " + invocation + " } }"); MethodTree testMethod = (MethodTree) ((ClassTreeImpl) compilationUnit.types().get(0)).members().get(1); ExpressionTree methodInvocation = ((ExpressionStatementTree) testMethod.block().body().get(0)).expression(); return methodInvocation.symbolType(); } private static final String CHAR = "(char) 42"; private static final String BYTE = "(byte) 42"; private static final String SHORT = "(short) 42"; private static final String INT = "(int) 42"; private static final String LONG = "(long) 42"; private static final String FLOAT = "(float) 42"; private static final String DOUBLE = "(double) 42"; private static final String BOOLEAN = "true"; private static final String NULL = "null"; private static final String STRING = "\"string\""; private CompilationUnitTree treeOf(String input) { CompilationUnitTree tree = parse(input); SemanticModel.createFor(tree, ImmutableList.<File>of()); return tree; } private static CompilationUnitTree parse(String input) { return (CompilationUnitTree) JavaParser.createParser(StandardCharsets.UTF_8).parse(input); } private JavaType typeOf(String input) { SemanticModel semanticModel = mock(SemanticModel.class); when(semanticModel.getEnv(any(Tree.class))).thenReturn(env); TypeAndReferenceSolver visitor = new TypeAndReferenceSolver(semanticModel, symbols, new Resolve(symbols, bytecodeCompleter, parametrizedTypeCache), parametrizedTypeCache); String p = "class Test { void wrapperMethod() { " + input + "; } }"; CompilationUnitTree tree = parse(p); tree.accept(visitor); TestedNodeExtractor testedNodeExtractor = new TestedNodeExtractor(false); testedNodeExtractor.visitCompilationUnit(tree); return visitor.getType(((ExpressionStatementTree) testedNodeExtractor.testedNode).expression()); } private JavaType typeOfExpression(String input) { SemanticModel semanticModel = mock(SemanticModel.class); when(semanticModel.getEnv(any(Tree.class))).thenReturn(env); TypeAndReferenceSolver visitor = new TypeAndReferenceSolver(semanticModel, symbols, new Resolve(symbols, bytecodeCompleter, parametrizedTypeCache), parametrizedTypeCache); String p = "class Test { void wrapperMethod() { Object o = " + input + "; } }"; CompilationUnitTree tree = parse(p); tree.accept(visitor); TestedNodeExtractor testedNodeExtractor = new TestedNodeExtractor(true); testedNodeExtractor.visitCompilationUnit(tree); return visitor.getType(testedNodeExtractor.testedNode); } private static class TestedNodeExtractor extends BaseTreeVisitor { private final boolean extractExpression; private Tree testedNode; public TestedNodeExtractor(boolean extractExpression) { this.extractExpression = extractExpression; } @Override public void visitMethod(MethodTree tree) { super.visitMethod(tree); if ("wrapperMethod".equals(tree.simpleName().name())) { testedNode = tree.block().body().get(0); if(extractExpression && testedNode.is(Tree.Kind.VARIABLE)) { testedNode = ((VariableTree)testedNode).initializer(); } } } } }