Skip to content

Commit

Permalink
fix: Keep implicit status for more implicit enclosing class references (
Browse files Browse the repository at this point in the history
  • Loading branch information
I-Al-Istannen authored Sep 9, 2023
1 parent 752b228 commit 30dadf1
Show file tree
Hide file tree
Showing 2 changed files with 102 additions and 2 deletions.
12 changes: 11 additions & 1 deletion src/main/java/spoon/support/compiler/jdt/ReferenceBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,17 @@ public boolean onAccess(char[][] tokens, int index) {
return true;
}
});
if (ref != null) {
// Equality ignores implicit state. The `accessedType` correctly models implicit parts, `ref` resolves a
// fully qualified name which might be accessed using an alias, e.g.
// class Foo { static class Inner {} }
// class Bar extends Foo {}
// Use: package.Bar.Inner
// Here the `accessedType` is `package.Foo.Inner`, but we want `package.Bar.Inner`, which is what
// getQualifiedTypeReference computes.
// This is still not perfect, as getQualifiedTypeReference can not resolve accesses like `Bar.Inner` within
// Bar.
if (ref != null && !ref.equals(accessedType)) {
JDTTreeBuilderHelper.handleImplicit(type, ref);
accessedType = ref;
}
}
Expand Down
92 changes: 91 additions & 1 deletion src/test/java/spoon/reflect/reference/CtTypeReferenceTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,23 @@
import org.mockito.stubbing.Answer;
import spoon.Launcher;
import spoon.compiler.Environment;
import spoon.reflect.declaration.CtField;
import spoon.reflect.declaration.CtParameter;
import spoon.reflect.declaration.CtType;
import spoon.reflect.factory.Factory;
import spoon.reflect.factory.TypeFactory;
import spoon.support.compiler.VirtualFile;
import spoon.support.modelobs.FineModelChangeListener;
import spoon.support.reflect.reference.CtTypeReferenceImpl;
import spoon.testing.utils.GitHubIssue;

import java.util.function.Function;
import java.util.function.Supplier;

import static com.google.common.primitives.Primitives.allPrimitiveTypes;
import static com.google.common.primitives.Primitives.wrap;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.when;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static spoon.testing.utils.Check.assertNotNull;

@ExtendWith(MockitoExtension.class)
Expand Down Expand Up @@ -121,4 +126,89 @@ void testGetActualClassForArray(String className, int arrayDepth, String expecte
);
}

@Test
void testImplicitInnerClassIsNotQualified() {
// contract: If the source code contains no explicit outer class reference, so does the model
Launcher launcher = new Launcher();
launcher.getEnvironment().setComplianceLevel(17);
launcher.getEnvironment().setAutoImports(true);
launcher.getEnvironment().setShouldCompile(true);
launcher.addInputResource(new VirtualFile(
"class TestInnerClass {\n" +
" static class A { static class B {} }\n" +
"\n" +
" A a = new A();\n" +
" A.B b = new A.B();\n" +
"}\n"
));

CtType<?> innerClass = launcher.buildModel().getAllTypes().iterator().next();
CtField<?> a = innerClass.getField("a");
assertEquals("A a = new A();", a.toString());
assertTrue(a.getType().getDeclaringType().isImplicit(), "Declaring access should be implicit");

CtField<?> b = innerClass.getField("b");
assertEquals("A.B b = new A.B();", b.toString());
assertTrue(
b.getType().getDeclaringType().getDeclaringType().isImplicit(),
"Declaring access should be implicit"
);
}

@Test
void testAliasAccessedClassIsNotQualified() {
// contract: If the source code contains no explicit outer class reference, so does the model
Launcher launcher = new Launcher();
launcher.getEnvironment().setComplianceLevel(17);
launcher.getEnvironment().setAutoImports(true);
launcher.getEnvironment().setShouldCompile(true);
launcher.addInputResource(new VirtualFile(
"class TestInnerClass {\n" +
" static class A { static class B {} }\n" +
"}\n",
"TestInnerClass.java"
));
launcher.addInputResource(new VirtualFile(
"class Inheritor extends TestInnerClass.A {\n" +
" public void foo(B b) {}\n" +
"}\n",
"Inheritor.java"
));

launcher.buildModel();
CtType<?> inheritor = launcher.getFactory().Type().get("Inheritor");
CtParameter<?> fooParam = inheritor.getMethodsByName("foo").get(0).getParameters().get(0);

// Not qualified
assertEquals("B b", fooParam.toString());
}

@Test
@GitHubIssue(issueNumber = -1, fixed = false)
void testAliasAccessedClassIsNotQualified2() {
// contract: If the source code contains no explicit outer class reference, so does the model
Launcher launcher = new Launcher();
launcher.getEnvironment().setComplianceLevel(17);
launcher.getEnvironment().setAutoImports(true);
launcher.getEnvironment().setShouldCompile(true);
launcher.addInputResource(new VirtualFile(
"class TestInnerClass {\n" +
" static class A { static class B {} }\n" +
"}\n",
"TestInnerClass.java"
));
launcher.addInputResource(new VirtualFile(
"class Inheritor extends TestInnerClass.A {\n" +
" public void foo(Inheritor.B b) {}\n" +
"}\n",
"Inheritor.java"
));

launcher.buildModel();
CtType<?> inheritor = launcher.getFactory().Type().get("Inheritor");
CtParameter<?> fooParam = inheritor.getMethodsByName("foo").get(0).getParameters().get(0);

assertEquals("Inheritor.B b", fooParam.toString());
}

}

0 comments on commit 30dadf1

Please sign in to comment.