openrewrite / rewrite

Automated mass refactoring of source code.
https://docs.openrewrite.org
Apache License 2.0
2.26k stars 338 forks source link

`JavaTemplate` can't type-attribute references to types from the same project #2927

Closed knutwannheden closed 3 months ago

knutwannheden commented 1 year ago

When JavaTemplate is used to modify a piece of code and the template contains expressions, where the types are derived from template parameters, which in turn as their type have some other top-level type in the same compilation unit, then the type attribution is missing after the replacement.

Here a test case which could be added to JavaTemplateTest, which demonstrates the problem. After the replacement the Collections.singletonList() method invocation has no type (J.MethodInvocation#methodType is null) because the stub generated by BlockStatementTemplateGenerator didn't contain the T2 type.

    void replaceWithReferenceToOtherTopLevelType() {
        rewriteRun(
          spec -> spec.recipe(toRecipe(() -> new JavaVisitor<>() {
              @Override
              public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext executionContext) {
                  if (method.getSimpleName().equals("asList")) {
                      method = method.withTemplate(JavaTemplate.builder(this::getCursor, "Collections.singletonList(#{any()})")
                          .imports("java.util.Collections").build(),
                        method.getCoordinates().replace(), method.getArguments().get(0));
                      maybeAddImport("java.util.Collections");
                      maybeRemoveImport("java.util.Arrays");
                  }
                  return method;
              }
          })),
          java(
            """
              import java.util.Arrays;

              class T {
                  void m() {
                      Object l = Arrays.asList(new T2());
                  }
              }
              class T2 {
              }
              """,
            """
              import java.util.Collections;

              class T {
                  void m() {
                      Object l = Collections.singletonList(new T2());
                  }
              }
              class T2 {
              }
              """
          )
        );
    }
jkschneider commented 1 year ago

this is why you basically shouldn’t ever use #{any()} but rather always specify a type

sambsnyd commented 1 year ago

Even if you provide a type from the project to #{any(com.type.from.Project)} the parser wont know about it unless a stub is constructed and passed in. The solution we've briefly discussed before is to generate stubs for any types present in the LST.

timtebeek commented 3 months ago

I believe this has been addressed with

We can reopen if I misunderstood, but given the timelines those seemed to have aligned.