diff --git a/src/main/java/spoon/reflect/visitor/filter/VariableAccessFilter.java b/src/main/java/spoon/reflect/visitor/filter/VariableAccessFilter.java index 4d0142dcf0f..731a9c62601 100644 --- a/src/main/java/spoon/reflect/visitor/filter/VariableAccessFilter.java +++ b/src/main/java/spoon/reflect/visitor/filter/VariableAccessFilter.java @@ -8,20 +8,21 @@ package spoon.reflect.visitor.filter; import spoon.reflect.code.CtVariableAccess; +import spoon.reflect.declaration.CtVariable; import spoon.reflect.reference.CtVariableReference; import spoon.reflect.visitor.Filter; /** - * This simple filter matches all the accesses to a given field. + * This simple filter matches all the accesses to a given variable. */ public class VariableAccessFilter> implements Filter { - CtVariableReference variable; + private final CtVariableReference variable; + private CtVariable variableDeclaration; /** - * Creates a new field access filter. + * Creates a new variable access filter. * - * @param variable - * the accessed variable + * @param variable the variable to find accesses for, must not be {@code null} */ public VariableAccessFilter(CtVariableReference variable) { if (variable == null) { @@ -32,7 +33,22 @@ public VariableAccessFilter(CtVariableReference variable) { @Override public boolean matches(T variableAccess) { - return variable.equals(variableAccess.getVariable()); + if (this.variable.equals(variableAccess.getVariable())) { + return true; + } + // If this.variable is a reference to a generic field, then the references might not be equal: + // + // Given `class A { T t; }`, the reference would be to `T t`, but an access to `t` could look + // like this: + // `A a = new A<>(); a.t = "foo";` + // ^^^ reference to `String t` + // As (String t) != (T t), the references are not equal, even though the same variable is accessed. + // Therefore, we fall back to comparing the declaration if the references were different. + if (this.variableDeclaration == null) { + this.variableDeclaration = this.variable.getDeclaration(); + } + + return this.variableDeclaration.equals(variableAccess.getVariable().getDeclaration()); } } diff --git a/src/test/java/spoon/test/filters/FilterTest.java b/src/test/java/spoon/test/filters/FilterTest.java index a21594544e7..ab6fc6fa40d 100644 --- a/src/test/java/spoon/test/filters/FilterTest.java +++ b/src/test/java/spoon/test/filters/FilterTest.java @@ -32,6 +32,7 @@ import spoon.reflect.code.CtNewClass; import spoon.reflect.code.CtStatement; import spoon.reflect.code.CtSwitch; +import spoon.reflect.code.CtVariableAccess; import spoon.reflect.declaration.CtClass; import spoon.reflect.declaration.CtElement; import spoon.reflect.declaration.CtExecutable; @@ -75,6 +76,7 @@ import spoon.reflect.visitor.filter.ReturnOrThrowFilter; import spoon.reflect.visitor.filter.SubtypeFilter; import spoon.reflect.visitor.filter.TypeFilter; +import spoon.reflect.visitor.filter.VariableAccessFilter; import spoon.support.comparator.DeepRepresentationComparator; import spoon.support.reflect.declaration.CtMethodImpl; import spoon.support.visitor.SubInheritanceHierarchyResolver; @@ -1430,4 +1432,27 @@ public boolean matches(CtElement element) { } + @Test + public void testVariableAccessFilterWithGenericField() { + // contract: VariableAccessFilter returns all accesses to a given field, even if it is generic + CtClass ctClass = Launcher.parseClass( + "public class Example {\n" + + " T field;\n" + + "\n" + + " public static void main(String[] args) {\n" + + " Example example = new Example<>();\n" + + "\n" + + " example.field = \"Hello\"; // write access to Example#field\n" + + " System.out.println(example.field); // read access to Example#field\n" + + " }\n" + + "}\n" + ); + + List> accesses = ctClass.getElements( + new VariableAccessFilter<>(ctClass.getField("field").getReference()) + ); + + assertEquals(2, accesses.size()); + } + }