Skip to content

Commit

Permalink
Lombok's generated @with method misses type information; fix for Java…
Browse files Browse the repository at this point in the history
… 17+ (#4882)

* Resolve type information for lombok @with for Java 17+

---------

Co-authored-by: Laurens Westerlaken <[email protected]>
  • Loading branch information
jevanlingen and Laurens-W authored Jan 13, 2025
1 parent 6f3416f commit aeb5bf2
Show file tree
Hide file tree
Showing 7 changed files with 152 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
import org.jspecify.annotations.Nullable;
import org.openrewrite.java.JavaTypeMapping;
import org.openrewrite.java.internal.JavaTypeCache;
import org.openrewrite.java.tree.Flag;
import org.openrewrite.java.tree.JavaType;
import org.openrewrite.java.tree.TypeUtils;

Expand Down Expand Up @@ -461,6 +460,10 @@ public JavaType.Primitive primitive(TypeTag tag) {
* @return Method type attribution.
*/
public JavaType.@Nullable Method methodInvocationType(com.sun.tools.javac.code.@Nullable Type selectType, @Nullable Symbol symbol) {
/*
TODO: AttrRecover class does not exist for java 11; there is JCNoType
in the Type.class, so maybe it is possible to retrieve this information...
*/
if (selectType == null || selectType instanceof Type.ErrorType || symbol == null || symbol.kind == Kinds.Kind.ERR || symbol.type instanceof Type.UnknownType) {
return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -962,11 +962,15 @@ public J visitMethodInvocation(MethodInvocationTree node, Space fmt) {
singletonList(padRight(new J.Empty(randomId(), sourceBefore(")"), Markers.EMPTY), EMPTY)) :
convertAll(node.getArguments(), commaDelim, t -> sourceBefore(")")), Markers.EMPTY);

Symbol methodSymbol = (jcSelect instanceof JCFieldAccess) ? ((JCFieldAccess) jcSelect).sym :
((JCIdent) jcSelect).sym;
JavaType.Method methodType;
if (name.getType() instanceof JavaType.Method) {
methodType = (JavaType.Method) name.getType();
} else {
Symbol methodSymbol = (jcSelect instanceof JCFieldAccess) ? ((JCFieldAccess) jcSelect).sym : ((JCIdent) jcSelect).sym;
methodType = typeMapping.methodInvocationType(jcSelect.type, methodSymbol);
}

return new J.MethodInvocation(randomId(), fmt, Markers.EMPTY, select, typeParams, name, args,
typeMapping.methodInvocationType(jcSelect.type, methodSymbol));
return new J.MethodInvocation(randomId(), fmt, Markers.EMPTY, select, typeParams, name, args, methodType);
}

@Override
Expand Down Expand Up @@ -1804,7 +1808,7 @@ private void reportJavaParsingException(Throwable ex) {
if (endPos(t) == cursor && rightPadded.getElement() instanceof J.Erroneous) {
cursor += ((J.Erroneous) rightPadded.getElement()).getText().length();
} else {
cursor(max(endPos(t), cursor)); // if there is a non-empty suffix, the cursor may have already moved past it
cursor(max(endPos(t), cursor)); // if there is a non-empty suffix or a lombok generated method, the cursor can be already moved past it
}
return rightPadded;
}
Expand Down Expand Up @@ -1896,7 +1900,7 @@ private Space statementDelim(@Nullable Tree t) {
case EXPRESSION_STATEMENT:
ExpressionTree expTree = ((ExpressionStatementTree) t).getExpression();
if (expTree instanceof ErroneousTree) {
return Space.build(source.substring(((JCTree) expTree).getEndPosition(endPosTable),((JCTree) t).getEndPosition(endPosTable)), Collections.emptyList());
return Space.build(source.substring(((JCTree) expTree).getEndPosition(endPosTable), ((JCTree) t).getEndPosition(endPosTable)), Collections.emptyList());
} else {
return sourceBefore(";");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,20 @@

import com.sun.source.tree.Tree;
import com.sun.tools.javac.code.*;
import com.sun.tools.javac.comp.AttrRecover;
import com.sun.tools.javac.tree.JCTree;
import lombok.RequiredArgsConstructor;
import org.jspecify.annotations.Nullable;
import org.openrewrite.java.JavaTypeMapping;
import org.openrewrite.java.internal.JavaTypeCache;
import org.openrewrite.java.tree.Flag;
import org.openrewrite.java.tree.JavaType;
import org.openrewrite.java.tree.TypeUtils;

import javax.lang.model.type.NullType;
import javax.lang.model.type.TypeMirror;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
Expand Down Expand Up @@ -370,7 +371,7 @@ private JavaType.FullyQualified.Kind getKind(Symbol.ClassSymbol sym) {
if (type == null && symbol != null) {
type = symbol.type;
}
if (type instanceof Type.MethodType || type instanceof Type.ForAll) {
if (type instanceof Type.MethodType || type instanceof Type.ForAll || (type instanceof Type.ErrorType && type.getOriginalType() instanceof Type.MethodType)) {
return methodInvocationType(type, symbol);
}
return type(type);
Expand Down Expand Up @@ -459,6 +460,22 @@ public JavaType.Primitive primitive(TypeTag tag) {
* @return Method type attribution.
*/
public JavaType.@Nullable Method methodInvocationType(com.sun.tools.javac.code.@Nullable Type selectType, @Nullable Symbol symbol) {
if (selectType instanceof Type.ErrorType) {
try {
// Ugly reflection solution, because AttrRecover$RecoveryErrorType is private inner class
for (Class<?> targetClass : Class.forName(AttrRecover.class.getCanonicalName()).getDeclaredClasses()) {
if (targetClass.getSimpleName().equals("RecoveryErrorType")) {
Field field = targetClass.getDeclaredField("candidateSymbol");
field.setAccessible(true);
Symbol originalSymbol = (Symbol) field.get(selectType);
return methodInvocationType(selectType.getOriginalType(), originalSymbol);
}
}
} catch (Exception e) {
// ignore
}
}

if (selectType == null || selectType instanceof Type.ErrorType || symbol == null || symbol.kind == Kinds.Kind.ERR || symbol.type instanceof Type.UnknownType) {
return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -962,11 +962,15 @@ public J visitMethodInvocation(MethodInvocationTree node, Space fmt) {
singletonList(padRight(new J.Empty(randomId(), sourceBefore(")"), Markers.EMPTY), EMPTY)) :
convertAll(node.getArguments(), commaDelim, t -> sourceBefore(")")), Markers.EMPTY);

Symbol methodSymbol = (jcSelect instanceof JCFieldAccess) ? ((JCFieldAccess) jcSelect).sym :
((JCIdent) jcSelect).sym;
JavaType.Method methodType;
if (name.getType() instanceof JavaType.Method) {
methodType = (JavaType.Method) name.getType();
} else {
Symbol methodSymbol = (jcSelect instanceof JCFieldAccess) ? ((JCFieldAccess) jcSelect).sym : ((JCIdent) jcSelect).sym;
methodType = typeMapping.methodInvocationType(jcSelect.type, methodSymbol);
}

return new J.MethodInvocation(randomId(), fmt, Markers.EMPTY, select, typeParams, name, args,
typeMapping.methodInvocationType(jcSelect.type, methodSymbol));
return new J.MethodInvocation(randomId(), fmt, Markers.EMPTY, select, typeParams, name, args, methodType);
}

@Override
Expand Down Expand Up @@ -1804,7 +1808,7 @@ private void reportJavaParsingException(Throwable ex) {
if (endPos(t) == cursor && rightPadded.getElement() instanceof J.Erroneous) {
cursor += ((J.Erroneous) rightPadded.getElement()).getText().length();
} else {
cursor(max(endPos(t), cursor)); // if there is a non-empty suffix, the cursor may have already moved past it
cursor(max(endPos(t), cursor)); // if there is a non-empty suffix or a lombok generated method, the cursor can be already moved past it
}
return rightPadded;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,20 @@

import com.sun.source.tree.Tree;
import com.sun.tools.javac.code.*;
import com.sun.tools.javac.comp.AttrRecover;
import com.sun.tools.javac.tree.JCTree;
import lombok.RequiredArgsConstructor;
import org.jspecify.annotations.Nullable;
import org.openrewrite.java.JavaTypeMapping;
import org.openrewrite.java.internal.JavaTypeCache;
import org.openrewrite.java.tree.Flag;
import org.openrewrite.java.tree.JavaType;
import org.openrewrite.java.tree.TypeUtils;

import javax.lang.model.type.NullType;
import javax.lang.model.type.TypeMirror;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
Expand Down Expand Up @@ -381,7 +382,7 @@ private JavaType.FullyQualified.Kind getKind(Symbol.ClassSymbol sym) {
}

private @Nullable JavaType type(Type type, Symbol symbol) {
if (type instanceof Type.MethodType || type instanceof Type.ForAll) {
if (type instanceof Type.MethodType || type instanceof Type.ForAll || (type instanceof Type.ErrorType && type.getOriginalType() instanceof Type.MethodType)) {
return methodInvocationType(type, symbol);
}
return type(type);
Expand Down Expand Up @@ -470,6 +471,22 @@ public JavaType.Primitive primitive(TypeTag tag) {
* @return Method type attribution.
*/
public JavaType.@Nullable Method methodInvocationType(com.sun.tools.javac.code.@Nullable Type selectType, @Nullable Symbol symbol) {
if (selectType instanceof Type.ErrorType) {
try {
// Ugly reflection solution, because AttrRecover$RecoveryErrorType is private inner class
for (Class<?> targetClass : Class.forName(AttrRecover.class.getCanonicalName()).getDeclaredClasses()) {
if (targetClass.getSimpleName().equals("RecoveryErrorType")) {
Field field = targetClass.getDeclaredField("candidateSymbol");
field.setAccessible(true);
Symbol originalSymbol = (Symbol) field.get(selectType);
return methodInvocationType(selectType.getOriginalType(), originalSymbol);
}
}
} catch (Exception e) {
// ignore
}
}

if (selectType == null || selectType instanceof Type.ErrorType || symbol == null || symbol.kind == Kinds.Kind.ERR || symbol.type instanceof Type.UnknownType) {
return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
import lombok.RequiredArgsConstructor;
import org.jspecify.annotations.Nullable;
import org.openrewrite.java.internal.JavaTypeCache;
import org.openrewrite.java.tree.Flag;
import org.openrewrite.java.tree.JavaType;
import org.openrewrite.java.tree.TypeUtils;

Expand Down Expand Up @@ -455,6 +454,10 @@ public JavaType.Primitive primitive(TypeTag tag) {
* @return Method type attribution.
*/
public JavaType.@Nullable Method methodInvocationType(com.sun.tools.javac.code.@Nullable Type selectType, @Nullable Symbol symbol) {
/*
TODO: AttrRecover class does not exist for java 8; there is JCNoType
in the Type.class, so maybe it is possible to retrieve this information...
*/
if (selectType == null || selectType instanceof Type.ErrorType || symbol == null || symbol.kind == Kinds.ERR || symbol.type instanceof Type.UnknownType) {
return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import org.junit.jupiter.api.Test;
import org.openrewrite.java.JavaParser;
import org.openrewrite.java.MinimumJava11;
import org.openrewrite.java.MinimumJava17;
import org.openrewrite.java.search.FindMissingTypes;
import org.openrewrite.test.RecipeSpec;
import org.openrewrite.test.RewriteTest;
Expand Down Expand Up @@ -373,7 +374,57 @@ public void foo() {
}

@Test
void gett() {
rewriteRun(
java(
"""
import lombok.Getter;
public class WithExample {
@Getter int age;
public WithExample(int age) {
this.age = age;
}
void test() {
int x = getAge();
}
}
"""
)
);
}

//TODO fix for Java 8 and 11
@Test
@MinimumJava17
void with() {
rewriteRun(
java(
"""
import lombok.With;
public class WithExample {
@With int age;
public WithExample(int age) {
this.age = age;
}
void test() {
WithExample x = withAge("name", 23);
}
}
"""
)
);
}

//TODO fix for Java 8 and 11
@Test
@MinimumJava17
void withWithParams() {
rewriteRun(
java(
"""
Expand All @@ -389,6 +440,42 @@ public WithExample(@NonNull String name, int age) {
this.name = name;
this.age = age;
}
static void test() {
WithExample x = new WithExample("old name", 22);
x.withName("name", 23);
}
}
"""
)
);
}

//TODO fix for Java 8 and 11
@Test
@MinimumJava17
void withOnClass() {
rewriteRun(
java(
"""
import lombok.AccessLevel;
import lombok.NonNull;
import lombok.With;
@With
public class WithExample {
private final String name;
private final int age;
public WithExample(String name, int age) {
this.name = name;
this.age = age;
}
void test() {
WithExample x = new WithExample("old name", 22);
x.withName("name", 23);
}
}
"""
)
Expand Down

0 comments on commit aeb5bf2

Please sign in to comment.