Skip to content

Commit 3152620

Browse files
authored
fix(jdk-codemodel): resolve receiver type for unqualified method calls (#13)
1 parent 4b2c500 commit 3152620

3 files changed

Lines changed: 55 additions & 2 deletions

File tree

jdk-codemodel/src/main/java/build/codemodel/jdk/JdkExpressionConverter.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ public class JdkExpressionConverter
119119
private Trees trees;
120120
private CompilationUnitTree compilationUnit;
121121
private Function<TypeMirror, TypeUsage> typeResolver;
122+
private TypeUsage enclosingType;
122123

123124
/**
124125
* Creates a {@link JdkExpressionConverter}.
@@ -155,6 +156,16 @@ public void setTypeContext(final Trees trees,
155156
this.typeResolver = typeResolver;
156157
}
157158

159+
/**
160+
* Sets the type of the class currently being processed, used as the implicit receiver type for
161+
* unqualified method calls (e.g. {@code foo()} where the receiver is {@code this}).
162+
*
163+
* @param enclosingType the {@link TypeUsage} of the enclosing class
164+
*/
165+
public void setEnclosingType(final TypeUsage enclosingType) {
166+
this.enclosingType = enclosingType;
167+
}
168+
158169
/**
159170
* Converts the given {@link ExpressionTree} to a model {@link Expression}.
160171
* Returns {@link NullLiteral} for a {@code null} tree.
@@ -223,6 +234,7 @@ public Expression visitMethodInvocation(final MethodInvocationTree t, final Void
223234
} else {
224235
target = null;
225236
methodName = t.getMethodSelect().toString();
237+
receiverType = Optional.ofNullable(enclosingType);
226238
}
227239
final var args = t.getArguments().stream()
228240
.map(this::convert)

jdk-codemodel/src/main/java/build/codemodel/jdk/JdkInitializer.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,7 @@ public Void visitClass(final ClassTree classTree, final Void unused) {
214214
final var typeElement = (TypeElement) trees.getElement(classPath);
215215
if (typeElement != null
216216
&& !typeElement.getQualifiedName().toString().isEmpty()) {
217+
exprConverter.setEnclosingType(resolveTypeUsage(typeElement.asType(), null));
217218
processTypeElement(typeElement, classTree, cut);
218219
}
219220
return super.visitClass(classTree, unused);

jdk-codemodel/src/test/java/build/codemodel/jdk/MethodInvocationReceiverTypeTests.java

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ public void run(StringBuilder sb) {
9191
}
9292

9393
@Test
94-
void shouldReturnEmptyReceiverTypeForUnqualifiedCall() {
94+
void shouldResolveReceiverTypeForUnqualifiedCallToEnclosingClass() {
9595
final var source = JavaFileObjects.forSourceString(
9696
"build.codemodel.jdk.example.Caller", """
9797
package build.codemodel.jdk.example;
@@ -119,6 +119,46 @@ public void run() {
119119
final var invocation = (MethodInvocation) stmt.expression();
120120

121121
assertThat(invocation.methodName()).isEqualTo("bar");
122-
assertThat(invocation.receiverType()).isEmpty();
122+
assertThat(invocation.receiverType()).isPresent();
123+
assertThat(invocation.receiverType().get()).isInstanceOf(NamedTypeUsage.class);
124+
final var receiverTypeName = ((NamedTypeUsage) invocation.receiverType().get()).typeName();
125+
assertThat(receiverTypeName.name().toString()).isEqualTo("Caller");
126+
}
127+
128+
@Test
129+
void shouldResolveReceiverTypeForUnqualifiedCallInInnerClass() {
130+
final var source = JavaFileObjects.forSourceString(
131+
"build.codemodel.jdk.example.Outer", """
132+
package build.codemodel.jdk.example;
133+
public class Outer {
134+
public class Inner {
135+
public void helper() {}
136+
public void run() {
137+
helper();
138+
}
139+
}
140+
}
141+
""");
142+
143+
final var codeModel = JdkInitializerTests.runInternal(
144+
new JdkInitializer(List.of(), List.of(), List.of(source)));
145+
146+
final var innerTypeName = codeModel.getNameProvider()
147+
.getTypeName(Optional.empty(), "build.codemodel.jdk.example.Outer.Inner");
148+
final var innerDescriptor = codeModel.getTypeDescriptor(innerTypeName).orElseThrow();
149+
150+
final var run = innerDescriptor.traits(MethodDescriptor.class)
151+
.filter(m -> m.methodName().name().toString().equals("run"))
152+
.findFirst().orElseThrow();
153+
154+
final var body = run.getTrait(MethodBodyDescriptor.class).orElseThrow().body();
155+
final var stmt = (ExpressionStatement) body.statements().findFirst().orElseThrow();
156+
final var invocation = (MethodInvocation) stmt.expression();
157+
158+
assertThat(invocation.methodName()).isEqualTo("helper");
159+
assertThat(invocation.receiverType()).isPresent();
160+
assertThat(invocation.receiverType().get()).isInstanceOf(NamedTypeUsage.class);
161+
final var receiverTypeName = ((NamedTypeUsage) invocation.receiverType().get()).typeName();
162+
assertThat(receiverTypeName.name().toString()).isEqualTo("Inner");
123163
}
124164
}

0 commit comments

Comments
 (0)