diff --git a/src/main/java/spoon/support/compiler/jdt/JDTTreeBuilderHelper.java b/src/main/java/spoon/support/compiler/jdt/JDTTreeBuilderHelper.java index e9446c943c1..2c7033e5f44 100644 --- a/src/main/java/spoon/support/compiler/jdt/JDTTreeBuilderHelper.java +++ b/src/main/java/spoon/support/compiler/jdt/JDTTreeBuilderHelper.java @@ -659,7 +659,7 @@ CtExpression createTargetFieldAccess(QualifiedNameReference qualifiedNameRefe } else if (ref.isStatic()) { target = createTypeAccess(qualifiedNameReference, ref); } else if (!ref.isStatic() && !ref.getDeclaringType().isAnonymous()) { - if (ref.getDeclaringType().getDeclaredField(ref.getSimpleName()) == null) { + if (!JDTTreeBuilderQuery.isResolvedField(qualifiedNameReference)) { target = createTypeAccessNoClasspath(qualifiedNameReference); } else { target = jdtTreeBuilder.getFactory().Code().createThisAccess(jdtTreeBuilder.getReferencesBuilder().getTypeReference(qualifiedNameReference.actualReceiverType), true); diff --git a/src/main/java/spoon/support/compiler/jdt/JDTTreeBuilderQuery.java b/src/main/java/spoon/support/compiler/jdt/JDTTreeBuilderQuery.java index 4ff4fb97381..2bf021bc0b7 100644 --- a/src/main/java/spoon/support/compiler/jdt/JDTTreeBuilderQuery.java +++ b/src/main/java/spoon/support/compiler/jdt/JDTTreeBuilderQuery.java @@ -207,6 +207,19 @@ static boolean isValidProblemBindingField(QualifiedNameReference qualifiedNameRe ((FieldBinding) qualifiedNameReference.binding).declaringClass.compoundName); } + /** + * Check if the name reference is resolved in the JDT tree, i.e. that the declaration is available. + * + * @param qualifiedNameReference + * Reference which should contain a field binding. + * @return true if the field has been resolved by the jdt builder. + */ + static boolean isResolvedField(QualifiedNameReference qualifiedNameReference) { + return qualifiedNameReference.binding instanceof FieldBinding + && ((FieldBinding) qualifiedNameReference.binding).original().sourceField() != null; + } + + /** * Checks if the last node in the stack in the context is an assignment and have a lhs equals to the given expression. * diff --git a/src/test/java/spoon/test/targeted/TargetedExpressionTest.java b/src/test/java/spoon/test/targeted/TargetedExpressionTest.java index b1456cd594c..bc9288ab7eb 100644 --- a/src/test/java/spoon/test/targeted/TargetedExpressionTest.java +++ b/src/test/java/spoon/test/targeted/TargetedExpressionTest.java @@ -301,6 +301,22 @@ public void testOnlyStaticTargetFieldReadNoClasspath() { assertEquals("Launcher", ((CtTypeAccess) target).getAccessedType().getSimpleName()); } + @Test + public void testNestedClassAccessEnclosingTypeFieldNoClasspath() { + // Checks that a nested class accessing a field of an enclosing type's non-static field correctly + // resolves to a non-static field access. See https://github.com/INRIA/spoon/issues/3334 for details. + final Launcher launcher = new Launcher(); + launcher.getEnvironment().setNoClasspath(true); + launcher.addInputResource("./src/test/resources/spoon/test/noclasspath/targeted/Outer.java"); + CtModel model = launcher.buildModel(); + + List> fieldReads = model.getElements(e -> e.getVariable().getSimpleName().equals("cls")); + assertEquals(1, fieldReads.size()); + CtFieldRead fieldRead = fieldReads.get(0); + + assertTrue(fieldRead.getTarget() instanceof CtThisAccess); + } + @Test public void testTargetsOfInv() throws Exception { // contract: Specify declaring type of the executable of an invocation, the target of the invocation and its result. diff --git a/src/test/resources/spoon/test/noclasspath/targeted/Outer.java b/src/test/resources/spoon/test/noclasspath/targeted/Outer.java new file mode 100644 index 00000000000..bf77cf74349 --- /dev/null +++ b/src/test/resources/spoon/test/noclasspath/targeted/Outer.java @@ -0,0 +1,9 @@ +public class Outer { + SomeClass cls = new SomeClass(); + + private class Inner { + public void testMethod() { + int a = cls.val; + } + } +} \ No newline at end of file