/*
* Copyright 2013 Google Inc. All rights reserved.
*
* 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.errorprone.refaster;
import static org.junit.Assert.fail;
import com.google.common.collect.ImmutableClassToInstanceMap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Sets;
import com.sun.source.tree.Tree.Kind;
import com.sun.tools.javac.code.BoundKind;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.JCTree.JCCompilationUnit;
import com.sun.tools.javac.tree.TreeScanner;
import java.lang.annotation.Annotation;
import java.util.Set;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/**
* Test {@link UTree#unify} against real compiled ASTs.
*
* @author lowasser@google.com (Louis Wasserman)
*/
@RunWith(JUnit4.class)
public class UnificationTest extends CompilerBasedTest {
public void expectMatches(
final Template<?> template, Match... expected) {
final Set<Match> expectedMatches = Sets.newHashSet(expected);
TreeScanner matchScanner = new TreeScanner() {
@Override
public void scan(JCTree tree) {
if (tree == null) {
return;
}
for (TemplateMatch templateMatch : template.match(tree, context)) {
Match match = Match.create(templateMatch);
if (!expectedMatches.remove(match)) {
fail(String.format("Unexpected match against template %s:%n%s", template, match));
}
}
super.scan(tree);
}
};
for (JCCompilationUnit unit : compilationUnits) {
matchScanner.scan(unit);
}
for (Match missingMatch : expectedMatches) {
fail(String.format(
"Expected match against template %s not found: %s", template, missingMatch));
}
}
@Test
public void binary() {
// template: (a + b) / 2
ExpressionTemplate template = ExpressionTemplate.create(
ImmutableMap.of(
"a", UPrimitiveType.INT,
"b", UPrimitiveType.INT),
UBinary.create(Kind.DIVIDE,
UParens.create(UBinary.create(
Kind.PLUS, UFreeIdent.create("a"), UFreeIdent.create("b"))),
ULiteral.intLit(2)),
UPrimitiveType.INT);
compile(
"import java.util.Random;",
"class BinaryExample {",
" public void example(int x, int y) {",
// positive examples
" System.out.println((3 + 5) / 2);",
" System.out.println((x + y) / 2 + 20);",
" System.err.println((y + new Random().nextInt()) / 2);",
// negative examples
" System.out.println((x - y) / 2);",
" System.out.println((x * y) / 2);",
" System.out.println((x + y) / 3);",
" System.out.println((x + 5L) / 2);",
" }",
"}");
expectMatches(template,
Match.create(ImmutableMap.of("a", "3", "b", "5")),
Match.create(ImmutableMap.of("a", "x", "b", "y")),
Match.create(ImmutableMap.of("a", "y", "b", "new Random().nextInt()")));
}
@Test
public void compoundAssignment() {
// template: String += int
ExpressionTemplate template = ExpressionTemplate.create(
ImmutableMap.of(
"str", UClassType.create("java.lang.String"),
"n", UPrimitiveType.INT),
UAssignOp.create(UFreeIdent.create("str"), Kind.PLUS_ASSIGNMENT, UFreeIdent.create("n")),
UClassType.create("java.lang.String"));
compile(
"class CompoundAssignmentExample {",
" public void example() {",
" String foo = \"\";",
" foo += 5;",
" foo += \"bar\";",
" foo += 10;",
" }",
"}");
expectMatches(template,
Match.create(ImmutableMap.of("str", "foo", "n", "5")),
Match.create(ImmutableMap.of("str", "foo", "n", "10")));
}
@Test
public void methodInvocation() {
// template : md.digest(str.getBytes())
UType byteArrayType = UArrayType.create(UPrimitiveType.BYTE);
ExpressionTemplate template = ExpressionTemplate.create(
ImmutableMap.of(
"md", UClassType.create("java.security.MessageDigest"),
"str", UClassType.create("java.lang.String")),
UMethodInvocation.create(
UMemberSelect.create(UFreeIdent.create("md"), "digest",
UMethodType.create(byteArrayType, byteArrayType)),
UMethodInvocation.create(
UMemberSelect.create(UFreeIdent.create("str"), "getBytes",
UMethodType.create(byteArrayType)))),
byteArrayType);
compile(
"import java.security.MessageDigest;",
"import java.security.NoSuchAlgorithmException;",
"import java.nio.charset.Charset;",
"class MethodInvocationExample {",
" public void example(MessageDigest digest, String string)",
" throws NoSuchAlgorithmException {",
// positive examples
" MessageDigest.getInstance(\"MD5\").digest(\"foo\".getBytes());",
" digest.digest(\"foo\".getBytes());",
" MessageDigest.getInstance(\"SHA1\").digest(string.getBytes());",
" digest.digest((string + 90).getBytes());",
// negative examples
" System.out.println(\"foo\".getBytes());",
" }",
"}");
expectMatches(template,
Match.create(ImmutableMap.of("md", "MessageDigest.getInstance(\"MD5\")", "str", "\"foo\"")),
Match.create(ImmutableMap.of("md", "digest", "str", "\"foo\"")),
Match.create(ImmutableMap.of("md", "MessageDigest.getInstance(\"SHA1\")", "str", "string")),
Match.create(ImmutableMap.of("md", "digest", "str", "(string + 90)")));
}
@Test
public void staticMethodInvocation() {
// Template: BigInteger.valueOf(int)
ExpressionTemplate template = ExpressionTemplate.create(
ImmutableMap.of("x", UPrimitiveType.INT),
UMethodInvocation.create(
UStaticIdent.create("java.math.BigInteger", "valueOf",
UMethodType.create(UClassType.create("java.math.BigInteger"),
UPrimitiveType.LONG)),
UFreeIdent.create("x")),
UClassType.create("java.math.BigInteger"));
compile(
"import java.math.BigInteger;",
"class Foo {",
" public void example(int x) {",
// positive examples
" BigInteger.valueOf(32);",
" BigInteger.valueOf(x * 15);",
" BigInteger.valueOf(Integer.parseInt(\"3\"));",
// negative examples
" BigInteger.valueOf(32L);",
" super.equals(32);",
" }",
"}");
expectMatches(template,
Match.create(ImmutableMap.of("x", "32")),
Match.create(ImmutableMap.of("x", "x * 15")),
Match.create(ImmutableMap.of("x", "Integer.parseInt(\"3\")")));
}
@Test
public void multipleMatch() {
ExpressionTemplate template = ExpressionTemplate.create(
ImmutableMap.of("x", UPrimitiveType.INT),
UBinary.create(Kind.MINUS, UFreeIdent.create("x"), UFreeIdent.create("x")),
UPrimitiveType.INT);
compile(
"import java.math.BigInteger;",
"class MultipleMatchExample {",
" public void example(int n) {",
// positive examples
" System.out.println(3 - 3);",
" BigInteger.valueOf((n * 2) - (n * 2));",
// negative examples
" System.err.println(3 - 3L);",
" System.err.println((n * 2) - n * 2);",
" }",
"}");
expectMatches(template,
Match.create(ImmutableMap.of("x", "3")),
Match.create(ImmutableMap.of("x", "(n * 2)")));
}
@Test
public void returnTypeMatters() {
/* Template:
* <E> List<E> template(List<E> list) {
* return Collections.unmodifiableList(list);
* }
*
* Note that Collections.unmodifiableList takes a List<? extends T>,
* but we're restricting to the case where the return type is the same.
*/
UTypeVar tVar = UTypeVar.create("T");
UTypeVar eVar = UTypeVar.create("E");
ExpressionTemplate template = ExpressionTemplate.create(
ImmutableClassToInstanceMap.<Annotation>builder().build(),
ImmutableList.of(eVar),
ImmutableMap.of("list", UClassType.create("java.util.List", eVar)),
UMethodInvocation.create(
UStaticIdent.create("java.util.Collections", "unmodifiableList",
UForAll.create(ImmutableList.of(tVar),
UMethodType.create(
UClassType.create("java.util.List", tVar),
UClassType.create("java.util.List",
UWildcardType.create(BoundKind.EXTENDS, tVar))))),
UFreeIdent.create("list")),
UClassType.create("java.util.List", eVar));
compile(
"import java.util.ArrayList;",
"import java.util.Collections;",
"import java.util.List;",
"class ReturnTypeMattersExample {",
" public void example() {",
// positive examples
" Collections.unmodifiableList(new ArrayList<String>());",
" List<Integer> ints = Collections.unmodifiableList(Collections.singletonList(1));",
// negative examples
" List<CharSequence> seqs = Collections.<CharSequence>unmodifiableList(",
" new ArrayList<String>());",
" }",
"}");
expectMatches(template,
Match.create(ImmutableMap.of(
"list", "new ArrayList<String>()",
"E", "java.lang.String")),
Match.create(ImmutableMap.of(
"list", "Collections.singletonList(1)",
"E", "java.lang.Integer")));
}
@Test
public void recursiveType() {
/*
* Template:
* <E extends Enum<E>> String example(E e) {
* return e.name();
* }
*/
UTypeVar eTypeVar = UTypeVar.create("E");
eTypeVar.setUpperBound(UClassType.create("java.lang.Enum", eTypeVar));
ExpressionTemplate template = ExpressionTemplate.create(
ImmutableClassToInstanceMap.<Annotation>builder().build(),
ImmutableList.of(eTypeVar),
ImmutableMap.of("value", eTypeVar),
UMethodInvocation.create(
UMemberSelect.create(UFreeIdent.create("value"), "name",
UMethodType.create(UClassType.create("java.lang.String")))),
UClassType.create("java.lang.String"));
compile(
"import java.math.RoundingMode;",
"class RecursiveTypeExample {",
" public void example() {",
" System.out.println(RoundingMode.FLOOR.name());",
" }",
"}");
expectMatches(template,
Match.create(ImmutableMap.of(
"value", "RoundingMode.FLOOR",
"E", "java.math.RoundingMode")));
}
@Test
public void blockTemplate() {
BlockTemplate blockTemplate = BlockTemplate.create(
// <E>
ImmutableList.of(UTypeVar.create("E")),
ImmutableMap.of(
// Collection<E> collection
"collection", UClassType.create("java.util.Collection", UTypeVar.create("E")),
// Comparator<? super E> comparator
"comparator", UClassType.create("java.util.Comparator",
UWildcardType.create(BoundKind.SUPER, UTypeVar.create("E")))),
UVariableDecl.create(
"list",
UTypeApply.create("java.util.List", UTypeVarIdent.create("E")),
UNewClass.create(UTypeApply.create("java.util.ArrayList", UTypeVarIdent.create("E")),
UFreeIdent.create("collection"))),
UExpressionStatement.create(
UMethodInvocation.create(
UStaticIdent.create("java.util.Collections", "sort",
UForAll.create(ImmutableList.of(UTypeVar.create("T")),
UMethodType.create(UPrimitiveType.VOID,
UClassType.create("java.util.List", UTypeVar.create("T")),
UClassType.create("java.util.Comparator",
UWildcardType.create(BoundKind.SUPER, UTypeVar.create("T")))))),
ULocalVarIdent.create("list"),
UFreeIdent.create("comparator"))));
compile(
"import java.util.ArrayList;",
"import java.util.Comparator;",
"import java.util.Collections;",
"import java.util.List;",
"class BlockTemplateExample {",
" public void example(Comparator<String> cmp) {",
" List<String> foo = new ArrayList<String>();",
" foo.add(\"bar\");",
" List<String> sorted = new ArrayList<String>(foo);",
" Collections.sort(sorted, cmp);",
" }",
"}");
expectMatches(blockTemplate,
Match.create(ImmutableMap.of(
"collection", "foo",
"comparator", "cmp",
"E", "java.lang.String",
"list", "sorted")));
}
@Test
public void ifBlockTemplate() {
/*
* Template:
*
* if (cond) {
* x = y;
* } else {
* x = z;
* }
*/
BlockTemplate blockTemplate = BlockTemplate.create(
ImmutableList.of(UTypeVar.create("T")),
ImmutableMap.of(
"cond", UPrimitiveType.BOOLEAN,
"x", UTypeVar.create("T"),
"y", UTypeVar.create("T"),
"z", UTypeVar.create("T")),
UIf.create(UFreeIdent.create("cond"),
UBlock.create(UExpressionStatement.create(
UAssign.create(UFreeIdent.create("x"), UFreeIdent.create("y")))),
UBlock.create(UExpressionStatement.create(
UAssign.create(UFreeIdent.create("x"), UFreeIdent.create("z"))))));
compile(
"class IfBlockExample {",
" public void example(String x) {",
" if (Math.random() > 0.5) {",
" x = \"foo\";",
" } else {",
" x = \"bar\";",
" }",
" }",
"}");
expectMatches(blockTemplate,
Match.create(ImmutableMap.of(
"cond", "(Math.random() > 0.5)",
"x", "x",
"y", "\"foo\"",
"z", "\"bar\"",
"T", "java.lang.String")));
}
@Test
public void newArray() {
// Template: new String[] {str}
ExpressionTemplate template = ExpressionTemplate.create(
ImmutableMap.of("str", UClassType.create("java.lang.String")),
UNewArray.create(
UClassIdent.create("java.lang.String"),
ImmutableList.<UExpression>of(),
ImmutableList.of(UFreeIdent.create("str"))),
UArrayType.create(UClassType.create("java.lang.String")));
compile(
"class Foo {",
" public void example() {",
// positive examples
" String[] array1 = new String[] {\"foo\"};",
// negative examples
" String[] array2 = {\"bar\"};",
" }",
"}");
expectMatches(template,
Match.create(ImmutableMap.of("str", "\"foo\"")));
}
}