diff options
author | Reinier Zwitserloot <r.zwitserloot@projectlombok.org> | 2020-01-08 01:06:35 +0100 |
---|---|---|
committer | Reinier Zwitserloot <r.zwitserloot@projectlombok.org> | 2020-01-08 01:06:35 +0100 |
commit | 9ad2bd563b001c0742d767fea9ddaaeb60400ec7 (patch) | |
tree | 82d9653d555b46b1a42f7c8086e87bd6aefd16a8 | |
parent | 0b0656ae3c38ae915c86e17c2744d5e3d8fc805c (diff) | |
download | lombok-9ad2bd563b001c0742d767fea9ddaaeb60400ec7.tar.gz lombok-9ad2bd563b001c0742d767fea9ddaaeb60400ec7.tar.bz2 lombok-9ad2bd563b001c0742d767fea9ddaaeb60400ec7.zip |
[fixes #788] lombok generated equals method plus a non-null-by-default annotation no longer clash.
13 files changed, 160 insertions, 6 deletions
diff --git a/doc/changelog.markdown b/doc/changelog.markdown index 988f1053..b1ad3247 100644 --- a/doc/changelog.markdown +++ b/doc/changelog.markdown @@ -4,6 +4,7 @@ Lombok Changelog ### v1.18.11 "Edgy Guinea Pig" * PLATFORM: Support for JDK13 (including `yield` in switch expressions, as well as delombok having a nicer style for arrow-style switch blocks, and text blocks). * FEATURE: You can now configure a builder's 'setter' prefixes via `@Builder(setterPrefix = "set")` for example. We discourage doing this, but if some library you use requires them, have at it. [Pull Request #2174](https://github.com/rzwitserloot/lombok/pull/2174], [Issue #1805](https://github.com/rzwitserloot/lombok/issues/1805). +* FEATURE: Tired of being unable to use `@javax.annotation.ParametersAreNonnullByDefault` or `@org.eclipse.jdt.annotation.NonNullByDefault` because then the equals method that lombok generates isn't valid? Fret no more; lombok will now add the appropriate `@Nullable` annotation to the parameter to avoid any issues. (package-level nonnullByDefault not supported, it must appear on the class for this feature to work). [Issue #788](https://github.com/rzwitserloot/lombok/issues/788) * BUGFIX: Referring to an inner class inside the generics on a class marked with `@SuperBuilder` would cause the error `wrong number of type arguments; required 3` [Issue #2262](https://github.com/rzwitserloot/lombok/issues/2262); fixed by github user [`@Lekanich`](https://github.com/rzwitserloot/lombok/issues/2262) - thank you! * BUGFIX: Some of the code generated by `@Builder` did not include `this.` prefixes when accessing fields. While semantically it didn't matter, if you use the 'add this prefix for field accesses' save action in eclipse, the save action would break. [Issue #2327](https://github.com/rzwitserloot/lombok/issues/2327) * BUGFIX: When lombok copies javadoc from fields to relevant methods, it should generate an appropriate `@return this` line if lombok copies the javadoc to a generated setter that is chainable (returns itself). It didn't do that when generating the 'setters' in a `@Builder`. Lombok also didn't generate an appropriate `@return` item for `@With` methods. The javadoc has also been updated slightly (the `this` reference in the javadoc is now rendered in a code tag).[Issue #2323](https://github.com/rzwitserloot/lombok/issues/2323) diff --git a/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java b/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java index 948902d5..1099afd5 100644 --- a/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java +++ b/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java @@ -745,6 +745,20 @@ public class EclipseHandlerUtil { } } + public static String scanForNearestAnnotation(EclipseNode node, String... anns) { + while (node != null) { + for (EclipseNode ann : node.down()) { + if (ann.getKind() != Kind.ANNOTATION) continue; + Annotation a = (Annotation) ann.get(); + TypeReference aType = a.type; + for (String annToFind : anns) if (typeMatches(annToFind, node, aType)) return annToFind; + } + node = node.up(); + } + + return null; + } + public static boolean hasNonNullAnnotations(EclipseNode node) { AbstractVariableDeclaration avd = (AbstractVariableDeclaration) node.get(); if (avd.annotations == null) return false; diff --git a/src/core/lombok/eclipse/handlers/HandleEqualsAndHashCode.java b/src/core/lombok/eclipse/handlers/HandleEqualsAndHashCode.java index 1bca4767..46474b07 100755 --- a/src/core/lombok/eclipse/handlers/HandleEqualsAndHashCode.java +++ b/src/core/lombok/eclipse/handlers/HandleEqualsAndHashCode.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2019 The Project Lombok Authors. + * Copyright (C) 2009-2020 The Project Lombok Authors. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -63,6 +63,7 @@ import org.eclipse.jdt.internal.compiler.ast.IfStatement; import org.eclipse.jdt.internal.compiler.ast.InstanceOfExpression; import org.eclipse.jdt.internal.compiler.ast.IntLiteral; import org.eclipse.jdt.internal.compiler.ast.LocalDeclaration; +import org.eclipse.jdt.internal.compiler.ast.MarkerAnnotation; import org.eclipse.jdt.internal.compiler.ast.MessageSend; import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration; import org.eclipse.jdt.internal.compiler.ast.NameReference; @@ -504,9 +505,33 @@ public class HandleEqualsAndHashCode extends EclipseAnnotationHandler<EqualsAndH return arr == null ? 0 : arr.length; } + /* + * scan method, then class, then enclosing classes, then package for the first of: + * javax.annotation.ParametersAreNonnullByDefault or javax.annotation.ParametersAreNullableByDefault. If it's the NN variant, generate javax.annotation.Nullable _on the type_. + * org.eclipse.jdt.annotation.NonNullByDefault -> org.eclipse.jdt.annotation.Nullable + */ + + private static final char[][] JAVAX_ANNOTATION_NULLABLE = Eclipse.fromQualifiedName("javax.annotation.Nullable"); + private static final char[][] ORG_ECLIPSE_JDT_ANNOTATION_NULLABLE = Eclipse.fromQualifiedName("org.eclipse.jdt.annotation.Nullable"); + public MethodDeclaration createEquals(EclipseNode type, Collection<Included<EclipseNode, EqualsAndHashCode.Include>> members, boolean callSuper, ASTNode source, FieldAccess fieldAccess, boolean needsCanEqual, List<Annotation> onParam) { int pS = source.sourceStart; int pE = source.sourceEnd; - long p = (long)pS << 32 | pE; + long p = (long) pS << 32 | pE; + + Annotation[] onParamType = null; + + String nearest = scanForNearestAnnotation(type, "javax.annotation.ParametersAreNullableByDefault", "javax.annotation.ParametersAreNonnullByDefault"); + if ("javax.annotation.ParametersAreNonnullByDefault".equals(nearest)) { + onParamType = new Annotation[1]; + onParamType[0] = new MarkerAnnotation(generateQualifiedTypeRef(source, JAVAX_ANNOTATION_NULLABLE), 0); + } + + nearest = scanForNearestAnnotation(type, "org.eclipse.jdt.annotation.NonNullByDefault"); + if (nearest != null) { + Annotation a = new MarkerAnnotation(generateQualifiedTypeRef(source, ORG_ECLIPSE_JDT_ANNOTATION_NULLABLE), 0); + if (onParamType != null) onParamType = new Annotation[] {onParamType[0], a}; + else onParamType = new Annotation[] {a}; + } MethodDeclaration method = new MethodDeclaration(((CompilationUnitDeclaration) type.top().get()).compilationResult); setGeneratedBy(method, source); @@ -526,7 +551,8 @@ public class HandleEqualsAndHashCode extends EclipseAnnotationHandler<EqualsAndH method.bits |= Eclipse.ECLIPSE_DO_NOT_TOUCH_FLAG; method.bodyStart = method.declarationSourceStart = method.sourceStart = source.sourceStart; method.bodyEnd = method.declarationSourceEnd = method.sourceEnd = source.sourceEnd; - TypeReference objectRef = new QualifiedTypeReference(TypeConstants.JAVA_LANG_OBJECT, new long[] { p, p, p }); + QualifiedTypeReference objectRef = new QualifiedTypeReference(TypeConstants.JAVA_LANG_OBJECT, new long[] { p, p, p }); + if (onParamType != null) objectRef.annotations = new Annotation[][] {null, null, onParamType}; setGeneratedBy(objectRef, source); method.arguments = new Argument[] {new Argument(new char[] { 'o' }, 0, objectRef, Modifier.FINAL)}; method.arguments[0].sourceStart = pS; method.arguments[0].sourceEnd = pE; diff --git a/src/core/lombok/javac/handlers/HandleEqualsAndHashCode.java b/src/core/lombok/javac/handlers/HandleEqualsAndHashCode.java index 2981bfa7..0d0369b9 100644 --- a/src/core/lombok/javac/handlers/HandleEqualsAndHashCode.java +++ b/src/core/lombok/javac/handlers/HandleEqualsAndHashCode.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2019 The Project Lombok Authors. + * Copyright (C) 2009-2020 The Project Lombok Authors. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -381,6 +381,20 @@ public class HandleEqualsAndHashCode extends JavacAnnotationHandler<EqualsAndHas Name otherName = typeNode.toName("other"); Name thisName = typeNode.toName("this"); + List<JCAnnotation> annsOnParamOnMethod = List.nil(); + + String nearest = scanForNearestAnnotation(typeNode, "org.eclipse.jdt.annotation.NonNullByDefault"); + if (nearest != null) { + JCAnnotation m = maker.Annotation(genTypeRef(typeNode, "org.eclipse.jdt.annotation.Nullable"), List.<JCExpression>nil()); + annsOnParamOnMethod = annsOnParamOnMethod.prepend(m); + } + + nearest = scanForNearestAnnotation(typeNode, "javax.annotation.ParametersAreNullableByDefault", "javax.annotation.ParametersAreNonnullByDefault"); + if ("javax.annotation.ParametersAreNonnullByDefault".equals(nearest)) { + JCAnnotation m = maker.Annotation(genTypeRef(typeNode, "javax.annotation.Nullable"), List.<JCExpression>nil()); + annsOnParamOnMethod = annsOnParamOnMethod.prepend(m); + } + JCAnnotation overrideAnnotation = maker.Annotation(genJavaLangTypeRef(typeNode, "Override"), List.<JCExpression>nil()); List<JCAnnotation> annsOnMethod = List.of(overrideAnnotation); CheckerFrameworkVersion checkerFramework = getCheckerFrameworkVersion(typeNode); @@ -388,7 +402,14 @@ public class HandleEqualsAndHashCode extends JavacAnnotationHandler<EqualsAndHas annsOnMethod = annsOnMethod.prepend(maker.Annotation(genTypeRef(typeNode, CheckerFrameworkVersion.NAME__SIDE_EFFECT_FREE), List.<JCExpression>nil())); } JCModifiers mods = maker.Modifiers(Flags.PUBLIC, annsOnMethod); - JCExpression objectType = genJavaLangTypeRef(typeNode, "Object"); + JCExpression objectType; + if (annsOnParamOnMethod.isEmpty()) { + objectType = genJavaLangTypeRef(typeNode, "Object"); + } else { + objectType = chainDots(typeNode, "java", "lang", "Object"); + objectType = maker.AnnotatedType(annsOnParamOnMethod, objectType); + } + JCExpression returnType = maker.TypeIdent(CTC_BOOLEAN); long finalFlag = JavacHandlerUtil.addFinalIfNeeded(0L, typeNode.getContext()); diff --git a/src/core/lombok/javac/handlers/JavacHandlerUtil.java b/src/core/lombok/javac/handlers/JavacHandlerUtil.java index b3a5cf90..9359b1ae 100644 --- a/src/core/lombok/javac/handlers/JavacHandlerUtil.java +++ b/src/core/lombok/javac/handlers/JavacHandlerUtil.java @@ -1400,6 +1400,19 @@ public class JavacHandlerUtil { return result.toList(); } + public static String scanForNearestAnnotation(JavacNode node, String... anns) { + while (node != null) { + for (JavacNode ann : node.down()) { + if (ann.getKind() != Kind.ANNOTATION) continue; + JCAnnotation a = (JCAnnotation) ann.get(); + for (String annToFind : anns) if (typeMatches(annToFind, node, a.annotationType)) return annToFind; + } + node = node.up(); + } + + return null; + } + public static boolean hasNonNullAnnotations(JavacNode node) { for (JavacNode child : node.down()) { if (child.getKind() == Kind.ANNOTATION) { diff --git a/src/utils/lombok/javac/JavacTreeMaker.java b/src/utils/lombok/javac/JavacTreeMaker.java index 84293f11..20f4b66d 100644 --- a/src/utils/lombok/javac/JavacTreeMaker.java +++ b/src/utils/lombok/javac/JavacTreeMaker.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013-2018 The Project Lombok Authors. + * Copyright (C) 2013-2020 The Project Lombok Authors. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -864,6 +864,12 @@ public class JavacTreeMaker { return invoke(TypeAnnotationWithAttributeOnly, a); } + //javac versions: 8 + private static final MethodId<JCExpression> AnnotatedType = MethodId("AnnotatedType", JCExpression.class, List.class, JCExpression.class); + public JCExpression AnnotatedType(List<JCAnnotation> annotations, JCExpression underlyingType) { + return invoke(AnnotatedType, annotations, underlyingType); + } + //javac versions: 6-8 private static final MethodId<JCStatement> Call = MethodId("Call"); public JCStatement Call(JCExpression apply) { diff --git a/test/transform/resource/after-delombok/EqualsAndHashCodeWithNNBD.java b/test/transform/resource/after-delombok/EqualsAndHashCodeWithNNBD.java new file mode 100644 index 00000000..6eaf9f36 --- /dev/null +++ b/test/transform/resource/after-delombok/EqualsAndHashCodeWithNNBD.java @@ -0,0 +1,30 @@ +import javax.annotation.ParametersAreNonnullByDefault; + +@ParametersAreNonnullByDefault +class EqualsAndHashCodeWithNNBD { + + @org.eclipse.jdt.annotation.NonNullByDefault + static class Inner { + @java.lang.Override + @java.lang.SuppressWarnings("all") + public boolean equals(final java.lang.@javax.annotation.Nullable @org.eclipse.jdt.annotation.Nullable Object o) { + if (o == this) return true; + if (!(o instanceof EqualsAndHashCodeWithNNBD.Inner)) return false; + final EqualsAndHashCodeWithNNBD.Inner other = (EqualsAndHashCodeWithNNBD.Inner) o; + if (!other.canEqual((java.lang.Object) this)) return false; + return true; + } + + @java.lang.SuppressWarnings("all") + protected boolean canEqual(final java.lang.Object other) { + return other instanceof EqualsAndHashCodeWithNNBD.Inner; + } + + @java.lang.Override + @java.lang.SuppressWarnings("all") + public int hashCode() { + final int result = 1; + return result; + } + } +}
\ No newline at end of file diff --git a/test/transform/resource/after-ecj/EqualsAndHashCodeWithNNBD.java b/test/transform/resource/after-ecj/EqualsAndHashCodeWithNNBD.java new file mode 100644 index 00000000..4781839d --- /dev/null +++ b/test/transform/resource/after-ecj/EqualsAndHashCodeWithNNBD.java @@ -0,0 +1,28 @@ +import javax.annotation.ParametersAreNonnullByDefault; +@ParametersAreNonnullByDefault class EqualsAndHashCodeWithNNBD { + static @lombok.EqualsAndHashCode @org.eclipse.jdt.annotation.NonNullByDefault class Inner { + Inner() { + super(); + } + public @java.lang.Override @java.lang.SuppressWarnings("all") boolean equals(final java.lang.@javax.annotation.Nullable @org.eclipse.jdt.annotation.Nullable Object o) { + if ((o == this)) + return true; + if ((! (o instanceof EqualsAndHashCodeWithNNBD.Inner))) + return false; + final EqualsAndHashCodeWithNNBD.Inner other = (EqualsAndHashCodeWithNNBD.Inner) o; + if ((! other.canEqual((java.lang.Object) this))) + return false; + return true; + } + protected @java.lang.SuppressWarnings("all") boolean canEqual(final java.lang.Object other) { + return (other instanceof EqualsAndHashCodeWithNNBD.Inner); + } + public @java.lang.Override @java.lang.SuppressWarnings("all") int hashCode() { + final int result = 1; + return result; + } + } + EqualsAndHashCodeWithNNBD() { + super(); + } +}
\ No newline at end of file diff --git a/test/transform/resource/before/EqualsAndHashCodeWithNNBD.java b/test/transform/resource/before/EqualsAndHashCodeWithNNBD.java new file mode 100644 index 00000000..dc86cb55 --- /dev/null +++ b/test/transform/resource/before/EqualsAndHashCodeWithNNBD.java @@ -0,0 +1,8 @@ +// version 8: +import javax.annotation.ParametersAreNonnullByDefault; +@ParametersAreNonnullByDefault +class EqualsAndHashCodeWithNNBD { + @lombok.EqualsAndHashCode @org.eclipse.jdt.annotation.NonNullByDefault + static class Inner { + } +} diff --git a/test/transform/resource/messages-delombok/EqualsAndHashCodeWithNNBD.java.messages b/test/transform/resource/messages-delombok/EqualsAndHashCodeWithNNBD.java.messages new file mode 100644 index 00000000..31259a51 --- /dev/null +++ b/test/transform/resource/messages-delombok/EqualsAndHashCodeWithNNBD.java.messages @@ -0,0 +1 @@ +5 package org.eclipse.jdt.annotation does not exist diff --git a/test/transform/resource/messages-ecj/EqualsAndHashCodeWithNNBD.java.messages b/test/transform/resource/messages-ecj/EqualsAndHashCodeWithNNBD.java.messages new file mode 100644 index 00000000..8fa7c194 --- /dev/null +++ b/test/transform/resource/messages-ecj/EqualsAndHashCodeWithNNBD.java.messages @@ -0,0 +1 @@ +2 The import javax.annotation.ParametersAreNonnullByDefault cannot be resolved diff --git a/test/transform/resource/messages-idempotent/EqualsAndHashCodeWithNNBD.java.messages b/test/transform/resource/messages-idempotent/EqualsAndHashCodeWithNNBD.java.messages new file mode 100644 index 00000000..76ed9da0 --- /dev/null +++ b/test/transform/resource/messages-idempotent/EqualsAndHashCodeWithNNBD.java.messages @@ -0,0 +1,3 @@ +6 package org.eclipse.jdt.annotation does not exist +10 package org.eclipse.jdt.annotation does not exist +10 annotation @javax.annotation.Nullable not applicable in this type context diff --git a/website/templates/features/EqualsAndHashCode.html b/website/templates/features/EqualsAndHashCode.html index 5a7b8166..5b54e027 100644 --- a/website/templates/features/EqualsAndHashCode.html +++ b/website/templates/features/EqualsAndHashCode.html @@ -53,6 +53,8 @@ </p><p> If a getter exists for a field to be included, it is called instead of using a direct field reference. This behaviour can be suppressed:<br /> <code>@EqualsAndHashCode(doNotUseGetters = true)</code> + </p><p> + If the class (or an enclosing class) has either the <code>@org.eclipse.jdt.annotation.NonNullByDefault</code> or the <code>@javax.annotation.ParametersAreNonnullByDefault</code> annotation, the parameter of the generated <code>equals</code> method will have the appropriate <code>@Nullable</code> annotation; JDK8+ is required when you do this. <span class="since">(since 1.18.12)</span>. </p> </@f.smallPrint> </@f.scaffold> |