aboutsummaryrefslogtreecommitdiff
path: root/src/core/lombok/eclipse/handlers
diff options
context:
space:
mode:
authorReinier Zwitserloot <r.zwitserloot@projectlombok.org>2021-03-24 06:20:02 +0100
committerReinier Zwitserloot <r.zwitserloot@projectlombok.org>2021-03-24 06:22:15 +0100
commit52a31bc4ae2806907194d32567a820c670670357 (patch)
tree0c5322c9c41fffe091d72834d9e5981f077e6c7b /src/core/lombok/eclipse/handlers
parent3d7754e269ec84604d43be97a684bb26b519c6b9 (diff)
downloadlombok-52a31bc4ae2806907194d32567a820c670670357.tar.gz
lombok-52a31bc4ae2806907194d32567a820c670670357.tar.bz2
lombok-52a31bc4ae2806907194d32567a820c670670357.zip
[records] [`@NonNull`] eclipse impl onfthe `@NonNull` on record components feature.
All tests passing.
Diffstat (limited to 'src/core/lombok/eclipse/handlers')
-rw-r--r--src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java20
-rw-r--r--src/core/lombok/eclipse/handlers/HandleNonNull.java185
2 files changed, 176 insertions, 29 deletions
diff --git a/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java b/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java
index defe8d34..70d98cc6 100644
--- a/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java
+++ b/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java
@@ -1889,26 +1889,6 @@ public class EclipseHandlerUtil {
}
/**
- * Checks if there is at least one constructor that is generated by lombok.
- *
- * @param node Any node that represents the Type (TypeDeclaration) to look in, or any child node thereof.
- */
- public static boolean lombokConstructorExists(EclipseNode node) {
- node = upToTypeNode(node);
- if (node != null && node.get() instanceof TypeDeclaration) {
- TypeDeclaration typeDecl = (TypeDeclaration) node.get();
- if (typeDecl.methods != null) for (AbstractMethodDeclaration def : typeDecl.methods) {
- if (!(def instanceof ConstructorDeclaration)) continue;
- if ((def.bits & ASTNode.IsDefaultConstructor | IsCanonicalConstructor) != 0) continue;
- if (isTolerate(node, def)) continue;
- if (getGeneratedBy(def) != null) return true;
- }
- }
-
- return false;
- }
-
- /**
* Inserts a field into an existing type. The type must represent a {@code TypeDeclaration}.
* The field carries the &#64;{@link SuppressWarnings}("all") annotation.
*/
diff --git a/src/core/lombok/eclipse/handlers/HandleNonNull.java b/src/core/lombok/eclipse/handlers/HandleNonNull.java
index 27e78d32..365eef33 100644
--- a/src/core/lombok/eclipse/handlers/HandleNonNull.java
+++ b/src/core/lombok/eclipse/handlers/HandleNonNull.java
@@ -22,11 +22,12 @@
package lombok.eclipse.handlers;
import static lombok.core.handlers.HandlerUtil.handleFlagUsage;
-import static lombok.eclipse.Eclipse.isPrimitive;
+import static lombok.eclipse.Eclipse.*;
import static lombok.eclipse.handlers.EclipseHandlerUtil.*;
+import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collections;
+import java.util.List;
import org.eclipse.jdt.internal.compiler.ast.ASTNode;
import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration;
@@ -36,19 +37,28 @@ import org.eclipse.jdt.internal.compiler.ast.Argument;
import org.eclipse.jdt.internal.compiler.ast.AssertStatement;
import org.eclipse.jdt.internal.compiler.ast.Assignment;
import org.eclipse.jdt.internal.compiler.ast.Block;
+import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.ConstructorDeclaration;
import org.eclipse.jdt.internal.compiler.ast.EqualExpression;
+import org.eclipse.jdt.internal.compiler.ast.ExplicitConstructorCall;
import org.eclipse.jdt.internal.compiler.ast.Expression;
+import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.FieldReference;
import org.eclipse.jdt.internal.compiler.ast.IfStatement;
import org.eclipse.jdt.internal.compiler.ast.MessageSend;
import org.eclipse.jdt.internal.compiler.ast.NullLiteral;
+import org.eclipse.jdt.internal.compiler.ast.QualifiedTypeReference;
import org.eclipse.jdt.internal.compiler.ast.SingleNameReference;
+import org.eclipse.jdt.internal.compiler.ast.SingleTypeReference;
import org.eclipse.jdt.internal.compiler.ast.Statement;
import org.eclipse.jdt.internal.compiler.ast.SynchronizedStatement;
+import org.eclipse.jdt.internal.compiler.ast.ThisReference;
import org.eclipse.jdt.internal.compiler.ast.ThrowStatement;
import org.eclipse.jdt.internal.compiler.ast.TryStatement;
+import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration;
import org.eclipse.jdt.internal.compiler.ast.TypeReference;
+import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
-import lombok.AccessLevel;
import lombok.ConfigurationKeys;
import lombok.NonNull;
import lombok.core.AST.Kind;
@@ -58,7 +68,6 @@ import lombok.eclipse.EcjAugments;
import lombok.eclipse.EclipseAST;
import lombok.eclipse.EclipseAnnotationHandler;
import lombok.eclipse.EclipseNode;
-import lombok.eclipse.handlers.HandleConstructor.SkipIfConstructorExists;
import lombok.spi.Provides;
@Provides
@@ -68,7 +77,6 @@ public class HandleNonNull extends EclipseAnnotationHandler<NonNull> {
private static final char[] CHECK_NOT_NULL = "checkNotNull".toCharArray();
public static final HandleNonNull INSTANCE = new HandleNonNull();
- private HandleConstructor handleConstructor = new HandleConstructor();
public void fix(EclipseNode method) {
for (EclipseNode m : method.down()) {
@@ -83,14 +91,146 @@ public class HandleNonNull extends EclipseAnnotationHandler<NonNull> {
}
}
+ private List<FieldDeclaration> getRecordComponents(EclipseNode typeNode) {
+ List<FieldDeclaration> list = new ArrayList<FieldDeclaration>();
+
+ for (EclipseNode child : typeNode.down()) {
+ if (child.getKind() == Kind.FIELD) {
+ FieldDeclaration fd = (FieldDeclaration) child.get();
+ if ((fd.modifiers & AccRecord) != 0) list.add(fd);
+ }
+ }
+
+ return list;
+ }
+
+ private EclipseNode addCompactConstructorIfNeeded(EclipseNode typeNode, EclipseNode annotationNode) {
+ // explicit Compact Constructor has bits set: Bit32, IsCanonicalConstructor (10).
+ // implicit Compact Constructor has bits set: Bit32, IsCanonicalConstructor (10), and IsImplicit (11).
+ // explicit constructor with long-form shows up as a normal constructor (Bit32 set, that's all), but the
+ // implicit CC is then also present and will presumably be stripped out in some later phase.
+
+ EclipseNode toRemove = null;
+ EclipseNode existingCompactConstructor = null;
+ List<FieldDeclaration> recordComponents = null;
+ for (EclipseNode child : typeNode.down()) {
+ if (!(child.get() instanceof ConstructorDeclaration)) continue;
+ ConstructorDeclaration cd = (ConstructorDeclaration) child.get();
+ if ((cd.bits & IsCanonicalConstructor) != 0) {
+ if ((cd.bits & IsImplicit) != 0) {
+ toRemove = child;
+ } else {
+ existingCompactConstructor = child;
+ }
+ } else {
+ // If this constructor has exact matching types vs. the record components,
+ // this is the canonical constructor in long form and we should not generate one.
+
+ if (recordComponents == null) recordComponents = getRecordComponents(typeNode);
+ int argLength = cd.arguments == null ? 0 : cd.arguments.length;
+ int compLength = recordComponents.size();
+ boolean isCanonical = argLength == compLength;
+ if (isCanonical) top: for (int i = 0; i < argLength; i++) {
+ TypeReference a = recordComponents.get(i).type;
+ TypeReference b = cd.arguments[i] == null ? null : cd.arguments[i].type;
+ // technically this won't match e.g. `java.lang.String` to just `String`;
+ // to use this feature you'll need to use the same way to write it, which seems
+ // like a fair requirement.
+ char[][] ta = getRawTypeName(a);
+ char[][] tb = getRawTypeName(b);
+ if (ta == null || tb == null || ta.length != tb.length) {
+ isCanonical = false;
+ break top;
+ }
+ for (int j = 0; j < ta.length; j++) {
+ if (!Arrays.equals(ta[j], tb[j])) {
+ isCanonical = false;
+ break top;
+ }
+ }
+ }
+ if (isCanonical) {
+ return null;
+ }
+ }
+ }
+ if (existingCompactConstructor != null) return existingCompactConstructor;
+ int posToInsert = -1;
+ TypeDeclaration td = (TypeDeclaration) typeNode.get();
+ if (toRemove != null) {
+ int idxToRemove = -1;
+ for (int i = 0; i < td.methods.length; i++) {
+ if (td.methods[i] == toRemove.get()) idxToRemove = i;
+ }
+ if (idxToRemove != -1) {
+ System.arraycopy(td.methods, idxToRemove + 1, td.methods, idxToRemove, td.methods.length - idxToRemove - 1);
+ posToInsert = td.methods.length - 1;
+ typeNode.removeChild(toRemove);
+ }
+ }
+ if (posToInsert == -1) {
+ AbstractMethodDeclaration[] na = new AbstractMethodDeclaration[td.methods.length + 1];
+ posToInsert = td.methods.length;
+ System.arraycopy(td.methods, 0, na, 0, posToInsert);
+ td.methods = na;
+ }
+
+ ConstructorDeclaration cd = new ConstructorDeclaration(((CompilationUnitDeclaration) typeNode.top().get()).compilationResult);
+ cd.modifiers = ClassFileConstants.AccPublic;
+ cd.bits = ASTNode.Bit32 | ECLIPSE_DO_NOT_TOUCH_FLAG | IsCanonicalConstructor;
+ cd.selector = td.name;
+ cd.constructorCall = new ExplicitConstructorCall(ExplicitConstructorCall.ImplicitSuper);
+ if (recordComponents == null) recordComponents = getRecordComponents(typeNode);
+ cd.arguments = new Argument[recordComponents.size()];
+ cd.statements = new Statement[recordComponents.size()];
+ cd.bits = IsCanonicalConstructor;
+
+ for (int i = 0; i < cd.arguments.length; i++) {
+ FieldDeclaration cmp = recordComponents.get(i);
+ cd.arguments[i] = new Argument(cmp.name, cmp.sourceStart, cmp.type, 0);
+ cd.arguments[i].bits = ASTNode.IsArgument | ASTNode.IgnoreRawTypeCheck | ASTNode.IsReachable;
+ FieldReference lhs = new FieldReference(cmp.name, 0);
+ lhs.receiver = new ThisReference(0, 0);
+ SingleNameReference rhs = new SingleNameReference(cmp.name, 0);
+ cd.statements[i] = new Assignment(lhs, rhs, cmp.sourceEnd);
+ }
+
+ setGeneratedBy(cd, annotationNode.get());
+ for (int i = 0; i < cd.arguments.length; i++) {
+ FieldDeclaration cmp = recordComponents.get(i);
+ cd.arguments[i].sourceStart = cmp.sourceStart;
+ cd.arguments[i].sourceEnd = cmp.sourceStart;
+ cd.arguments[i].declarationSourceEnd = cmp.sourceStart;
+ cd.arguments[i].declarationEnd = cmp.sourceStart;
+ }
+
+ td.methods[posToInsert] = cd;
+ cd.annotations = addSuppressWarningsAll(typeNode, cd, cd.annotations);
+ cd.annotations = addGenerated(typeNode, cd, cd.annotations);
+ return typeNode.add(cd, Kind.METHOD);
+ }
+
+ private static char[][] getRawTypeName(TypeReference a) {
+ if (a instanceof QualifiedTypeReference) return ((QualifiedTypeReference) a).tokens;
+ if (a instanceof SingleTypeReference) return new char[][] {((SingleTypeReference) a).token};
+ return null;
+ }
+
@Override public void handle(AnnotationValues<NonNull> annotation, Annotation ast, EclipseNode annotationNode) {
// Generating new methods is only possible during diet parse but modifying existing methods requires a full parse.
// As we need both for @NonNull we reset the handled flag during diet parse.
+
if (!annotationNode.isCompleteParse()) {
- EclipseNode typeNode = upToTypeNode(annotationNode);
- if (isRecordField(annotationNode.up()) && !lombokConstructorExists(typeNode)) {
- handleConstructor.generateAllArgsConstructor(typeNode, AccessLevel.PUBLIC, null, SkipIfConstructorExists.NO, Collections.<Annotation>emptyList(), annotationNode);
+ if (annotationNode.up().getKind() == Kind.FIELD) {
+ //Check if this is a record and we need to generate the compact form constructor.
+ EclipseNode typeNode = annotationNode.up().up();
+ if (typeNode.getKind() == Kind.TYPE) {
+ if (isRecord(typeNode)) {
+ addCompactConstructorIfNeeded(typeNode, annotationNode);
+ }
+ }
}
+
EcjAugments.ASTNode_handled.clear(ast);
return;
}
@@ -98,6 +238,16 @@ public class HandleNonNull extends EclipseAnnotationHandler<NonNull> {
handle0(ast, annotationNode, false);
}
+ private EclipseNode findCompactConstructor(EclipseNode typeNode) {
+ for (EclipseNode child : typeNode.down()) {
+ if (!(child.get() instanceof ConstructorDeclaration)) continue;
+ ConstructorDeclaration cd = (ConstructorDeclaration) child.get();
+ if ((cd.bits & IsCanonicalConstructor) != 0 && (cd.bits & IsImplicit) == 0) return child;
+ }
+
+ return null;
+ }
+
private void handle0(Annotation ast, EclipseNode annotationNode, boolean force) {
handleFlagUsage(annotationNode, ConfigurationKeys.NON_NULL_FLAG_USAGE, "@NonNull");
@@ -106,13 +256,26 @@ public class HandleNonNull extends EclipseAnnotationHandler<NonNull> {
// but in that case those handlers will take care of it. However, we DO check if the annotation is applied to
// a primitive, because those handlers trigger on any annotation named @NonNull and we only want the warning
// behaviour on _OUR_ 'lombok.NonNull'.
+ EclipseNode fieldNode = annotationNode.up();
+ EclipseNode typeNode = fieldNode.up();
try {
if (isPrimitive(((AbstractVariableDeclaration) annotationNode.up().get()).type)) {
annotationNode.addWarning("@NonNull is meaningless on a primitive.");
+ return;
}
} catch (Exception ignore) {}
+ if (isRecord(typeNode)) {
+ // well, these kinda double as parameters (of the compact constructor), so we do some work here.
+ // NB:Tthe diet parse run already added an explicit compact constructor if we need to take any actions.
+ EclipseNode compactConstructor = findCompactConstructor(typeNode);
+
+ if (compactConstructor != null) {
+ addNullCheckIfNeeded((AbstractMethodDeclaration) compactConstructor.get(), (AbstractVariableDeclaration) fieldNode.get(), annotationNode);
+ }
+ }
+
return;
}
@@ -154,6 +317,11 @@ public class HandleNonNull extends EclipseAnnotationHandler<NonNull> {
return;
}
+ addNullCheckIfNeeded(declaration, param, annotationNode);
+ paramNode.up().rebuild();
+ }
+
+ private void addNullCheckIfNeeded(AbstractMethodDeclaration declaration, AbstractVariableDeclaration param, EclipseNode annotationNode) {
// Possibly, if 'declaration instanceof ConstructorDeclaration', fetch declaration.constructorCall, search it for any references to our parameter,
// and if they exist, create a new method in the class: 'private static <T> T lombok$nullCheck(T expr, String msg) {if (expr == null) throw NPE; return expr;}' and
// wrap all references to it in the super/this to a call to this method.
@@ -202,7 +370,6 @@ public class HandleNonNull extends EclipseAnnotationHandler<NonNull> {
newStatements[skipOver] = nullCheck;
declaration.statements = newStatements;
}
- paramNode.up().rebuild();
}
public boolean isNullCheck(Statement stat) {