diff --git a/AUTHORS b/AUTHORS
index 4a6ef2fd..9ee773f5 100755
--- a/AUTHORS
+++ b/AUTHORS
@@ -63,6 +63,7 @@ Taiki Sugawara
Takuya Murakami
Thomas Darimont
Till Brychcy
+Tim te Beek
Victor Williams Stafusa da Silva
Yonatan Sherwin
Yun Zhi Lin
diff --git a/doc/changelog.markdown b/doc/changelog.markdown
index 7375d310..af8894f1 100644
--- a/doc/changelog.markdown
+++ b/doc/changelog.markdown
@@ -3,8 +3,11 @@ Lombok Changelog
### v1.18.47 "Edgy Guinea Pig"
* BUGFIX: `@SneakyThrows` usage on JDK26 no longer results in class files that require `lombok.jar` to be on the runtime classpath (which should not be neccessary). [#4040](https://github.com/projectlombok/lombok/issues/4022).
+* FEATURE: New [config key](https://projectlombok.org/features/configuration) `lombok.checkReturnValueAnnotation` (values: `none`, `lombok`; default: `none`) lets lombok generate `@lombok.CheckReturnValue` on generated methods where the return value should not be ignored, such as `@With` methods and `@Builder.build()`. A future lombok release may flip the default to `lombok`. [#4013](https://github.com/projectlombok/lombok/pull/4013).
* PROMOTION: `@SuperBuilder` has been promoted to the main package. Otherwise, no changes have been made to the annotation. The old experimental annotation will remain for a few versions, at which point it will be marked as a deprecated annotation. Eventually it'll be removed. If you had `lombok.config` configuration for this annotation, the configuration keys for this feature have been renamed.
* OLD-CRUFT: `lombok.experimental.Wither` and `lombok.Delegate` are deprecated remnants; these features were moved (to respectively `lombok.With` and `lombok.experimental.Delegate` over 5 years ago. They are now removed entirely. If your project is dependent on an older version of lombok which still has those; fret not, lombok still processes these annotations. It just no longer includes them in the jar.
+* FEATURE: CheckerFramework: Lombok now adds `@SideEffectFree` to constructors it makes if you have enabled checker framework via `lombok.config`.
+* BUGFIX: CheckerFramework: Lombok would add `@SideEffectFree` to the `build()` method of any generated builder, even if it is a builder for invoking a method; in that case, the side-effect-free nature of `build()` mirrors the side-effect-free nature of the method invocation you've built. Lombok now checks if the method it generated a builder for is side effect free / 'check return type'.
### v1.18.46 (April 22nd, 2026)
* PLATFORM: JDK26 support added [#4019](https://github.com/projectlombok/lombok/issues/4019).
diff --git a/src/core/lombok/CheckReturnValue.java b/src/core/lombok/CheckReturnValue.java
new file mode 100644
index 00000000..0736108f
--- /dev/null
+++ b/src/core/lombok/CheckReturnValue.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2025-2026 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
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package lombok;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Lombok adds this annotation to generated methods where the return value should not be ignored.
+ *
+ * For example, {@code @With} methods return a new instance, so ignoring the return value is always a bug.
+ * Similarly, {@code @Builder}'s {@code build()} method produces the built object.
+ *
+ * Static analysis tools (Error Prone, IntelliJ, SpotBugs) recognize {@code @CheckReturnValue}
+ * by simple class name, regardless of package, and will warn when the return value is discarded.
+ *
+ * If you want to opt in, you can add {@code lombok.checkReturnValueAnnotation = lombok} to
+ * {@code lombok.config}.
+ */
+@Target({ElementType.METHOD})
+@Retention(RetentionPolicy.CLASS)
+public @interface CheckReturnValue {
+}
diff --git a/src/core/lombok/ConfigurationKeys.java b/src/core/lombok/ConfigurationKeys.java
index 997c550b..6ea02ebe 100644
--- a/src/core/lombok/ConfigurationKeys.java
+++ b/src/core/lombok/ConfigurationKeys.java
@@ -25,6 +25,7 @@ import java.util.List;
import lombok.core.configuration.CallSuperType;
import lombok.core.configuration.CapitalizationStrategy;
+import lombok.core.configuration.CheckReturnValueFlavor;
import lombok.core.configuration.CheckerFrameworkVersion;
import lombok.core.configuration.ConfigurationKey;
import lombok.core.configuration.FlagUsageType;
@@ -90,6 +91,21 @@ public class ConfigurationKeys {
*/
public static final ConfigurationKey ADD_LOMBOK_GENERATED_ANNOTATIONS = new ConfigurationKey("lombok.addLombokGeneratedAnnotation", "Generate @lombok.Generated on all generated code (default: true).") {};
+ /**
+ * lombok configuration: {@code lombok.checkReturnValueAnnotation} += {@code lombok} / {@code androidx}.
+ *
+ * If set, lombok generates all requested annotation flavors indicating that a caller must use the returned value on all methods where it is sensible, such as {@code @With} methods and {@code @Builder}'s {@code build()} method.
+ *
+ * If value {@code lombok} is added, lombok generates {@code @lombok.CheckReturnValue}.
+ *
+ * If value {@code androidx} is added, lombok generates {@code @androidx.annotation.CheckResult}.
+ *
+ * The current default is no flavors (no annotations are added), but a future lombok release may flip the default {@code lombok}.
+ *
+ * NB: {@code build()} methods only get these annotations if the thing they are building for is a type or constructor. Applying {@code @Builder} to a method will not result in these annotations unless that method is itself so annotated.
+ */
+ public static final ConfigurationKey> CHECK_RETURN_VALUE_ANNOTATION = new ConfigurationKey>("lombok.checkReturnValueAnnotation", "Which @CheckReturnValue annotation flavor(s) to emit on generated methods. Values: lombok, androidx (default: none).") {};
+
/**
* lombok configuration: {@code lombok.extern.findbugs.addSuppressFBWarnings} = {@code true} | {@code false}.
*
diff --git a/src/core/lombok/core/configuration/CheckReturnValueFlavor.java b/src/core/lombok/core/configuration/CheckReturnValueFlavor.java
new file mode 100644
index 00000000..15b1225b
--- /dev/null
+++ b/src/core/lombok/core/configuration/CheckReturnValueFlavor.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2026 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
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package lombok.core.configuration;
+
+/** Used for lombok configuration to select which {@code @CheckReturnValue} annotation flavor(s) to emit on generated methods. */
+public enum CheckReturnValueFlavor implements MappedConfigEnum {
+ LOMBOK,
+ ANDROIDX,
+ ;
+
+ @Override public boolean matches(String value) {
+ return value.equals(this.name().toLowerCase());
+ }
+
+ @Override public String toString() {
+ return this.name().toLowerCase();
+ }
+
+ public String getMarkerAnnotationFullyQualifiedName() {
+ if (this == LOMBOK) return "lombok.CheckReturnValue";
+ if (this == ANDROIDX) return "androidx.annotation.CheckResult";
+ throw new IllegalStateException("bug");
+ }
+}
diff --git a/src/core/lombok/core/configuration/JacksonVersion.java b/src/core/lombok/core/configuration/JacksonVersion.java
index 480dd973..89af28ec 100644
--- a/src/core/lombok/core/configuration/JacksonVersion.java
+++ b/src/core/lombok/core/configuration/JacksonVersion.java
@@ -31,11 +31,13 @@ public enum JacksonVersion implements MappedConfigEnum {
@Override public boolean matches(String value) {
if (this == TWO) return "2".equals(value);
- return "3".equals(value);
+ if (this == THREE) return "3".equals(value);
+ throw new IllegalStateException("bug");
}
@Override public String toString() {
if (this == TWO) return "2";
- return "3";
+ if (this == THREE) return "3";
+ throw new IllegalStateException("bug");
}
}
diff --git a/src/core/lombok/core/handlers/HandlerUtil.java b/src/core/lombok/core/handlers/HandlerUtil.java
index 4f546352..f9dd3351 100644
--- a/src/core/lombok/core/handlers/HandlerUtil.java
+++ b/src/core/lombok/core/handlers/HandlerUtil.java
@@ -79,7 +79,7 @@ public class HandlerUtil {
return 43;
}
- public static final List NONNULL_ANNOTATIONS, BASE_COPYABLE_ANNOTATIONS, JACKSON_COPY_TO_GETTER_ANNOTATIONS, JACKSON_COPY_TO_SETTER_ANNOTATIONS, JACKSON_COPY_TO_BUILDER_SINGULAR_SETTER_ANNOTATIONS, JACKSON_COPY_TO_BUILDER_ANNOTATIONS;
+ public static final List NONNULL_ANNOTATIONS, BASE_COPYABLE_ANNOTATIONS, JACKSON_COPY_TO_GETTER_ANNOTATIONS, JACKSON_COPY_TO_SETTER_ANNOTATIONS, JACKSON_COPY_TO_BUILDER_SINGULAR_SETTER_ANNOTATIONS, JACKSON_COPY_TO_BUILDER_ANNOTATIONS, IMPLIES_CHECK_RETURN_VALUE_ANNOTATIONS;
static {
// This is a list of annotations with a __highly specific meaning__: All annotations in this list indicate that passing null for the relevant item is __never__ acceptable, regardless of settings or circumstance.
// In other words, things like 'this models a database table, and the db table column has a nonnull constraint', or 'this represents a web form, and if this is null, the form is invalid' __do not count__ and should not be in this list;
@@ -497,6 +497,13 @@ public class HandlerUtil {
"com.fasterxml.jackson.databind.annotation.JsonNaming",
"tools.jackson.databind.annotation.JsonNaming",
}));
+ IMPLIES_CHECK_RETURN_VALUE_ANNOTATIONS = Collections.unmodifiableList(Arrays.asList(new String[] {
+ "lombok.CheckReturnValue",
+ "android.annotation.CheckResult",
+ "androidx.annotation.CheckResult",
+ "org.checkerframework.dataflow.qual.SideEffectFree",
+ "org.checkerframework.dataflow.qual.Pure",
+ }));
}
/** Checks if the given name is a valid identifier.
diff --git a/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java b/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java
index 7eaaf7b6..68763853 100644
--- a/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java
+++ b/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java
@@ -116,6 +116,7 @@ import lombok.core.AnnotationValues;
import lombok.core.AnnotationValues.AnnotationValue;
import lombok.core.LombokImmutableList;
import lombok.core.TypeResolver;
+import lombok.core.configuration.CheckReturnValueFlavor;
import lombok.core.configuration.CheckerFrameworkVersion;
import lombok.core.configuration.NullAnnotationLibrary;
import lombok.core.configuration.NullCheckExceptionType;
@@ -790,6 +791,17 @@ public class EclipseHandlerUtil {
return false;
}
+ public static boolean hasCheckReturnValueImplicatingAnnotations(EclipseNode node, Annotation[] anns) {
+ if (anns == null) return false;
+ for (Annotation annotation : anns) {
+ TypeReference typeRef = annotation.type;
+ if (typeRef != null && typeRef.getTypeName() != null) {
+ for (String bn : IMPLIES_CHECK_RETURN_VALUE_ANNOTATIONS) if (typeMatches(bn, node, typeRef)) return true;
+ }
+ }
+ return false;
+ }
+
private static final Annotation[] EMPTY_ANNOTATIONS_ARRAY = new Annotation[0];
/**
@@ -2096,6 +2108,7 @@ public class EclipseHandlerUtil {
private static final char[][] JAKARTA_ANNOTATION_GENERATED = Eclipse.fromQualifiedName("jakarta.annotation.Generated");
private static final char[][] LOMBOK_GENERATED = Eclipse.fromQualifiedName("lombok.Generated");
private static final char[][] EDU_UMD_CS_FINDBUGS_ANNOTATIONS_SUPPRESSFBWARNINGS = Eclipse.fromQualifiedName("edu.umd.cs.findbugs.annotations.SuppressFBWarnings");
+ private static final Map checkReturnValueQualifiedNameCache = new HashMap();
public static Annotation[] addSuppressWarningsAll(EclipseNode node, ASTNode source, Annotation[] originalAnnotationArray) {
Annotation[] anns = originalAnnotationArray;
@@ -2126,6 +2139,18 @@ public class EclipseHandlerUtil {
return result;
}
+ public static Annotation[] addCheckReturnValue(EclipseNode node, ASTNode source, Annotation[] originalAnnotationArray) {
+ Annotation[] out = originalAnnotationArray;
+ List flavors = node.getAst().readConfiguration(ConfigurationKeys.CHECK_RETURN_VALUE_ANNOTATION);
+ for (CheckReturnValueFlavor flavor : flavors) {
+ char[][] fqn = checkReturnValueQualifiedNameCache.get(flavor);
+ if (fqn == null) checkReturnValueQualifiedNameCache.put(flavor, fqn = Eclipse.fromQualifiedName(flavor.getMarkerAnnotationFullyQualifiedName()));
+ out = addAnnotation(source, out, fqn);
+ }
+
+ return out;
+ }
+
static Annotation[] addAnnotation(ASTNode source, Annotation[] originalAnnotationArray, char[][] annotationTypeFqn) {
return addAnnotation(source, originalAnnotationArray, annotationTypeFqn, (ASTNode[]) null);
}
diff --git a/src/core/lombok/eclipse/handlers/HandleBuilder.java b/src/core/lombok/eclipse/handlers/HandleBuilder.java
index c39acdc7..51aa2f21 100755
--- a/src/core/lombok/eclipse/handlers/HandleBuilder.java
+++ b/src/core/lombok/eclipse/handlers/HandleBuilder.java
@@ -125,6 +125,7 @@ public class HandleBuilder extends EclipseAnnotationHandler {
static class BuilderJob {
CheckerFrameworkVersion checkerFramework;
EclipseNode parentType;
+ boolean checkReturnValue;
String builderMethodName, buildMethodName;
boolean isStatic;
TypeParameter[] typeParams;
@@ -299,6 +300,7 @@ public class HandleBuilder extends EclipseAnnotationHandler {
}
if (parent.get() instanceof TypeDeclaration) {
+ job.checkReturnValue = true;
if (!isClass(parent) && !isRecord(parent)) {
annotationNode.addError(BUILDER_NODE_NOT_SUPPORTED_ERR);
return;
@@ -366,6 +368,7 @@ public class HandleBuilder extends EclipseAnnotationHandler {
job.setBuilderClassName(job.replaceBuilderClassName(td.name));
if (!checkName("builderClassName", job.builderClassName, annotationNode)) return;
} else if (parent.get() instanceof ConstructorDeclaration) {
+ job.checkReturnValue = true;
ConstructorDeclaration cd = (ConstructorDeclaration) parent.get();
if (cd.typeParameters != null && cd.typeParameters.length > 0) {
annotationNode.addError("@Builder is not supported on constructors with constructor type parameters.");
@@ -382,6 +385,11 @@ public class HandleBuilder extends EclipseAnnotationHandler {
if (!checkName("builderClassName", job.builderClassName, annotationNode)) return;
} else if (parent.get() instanceof MethodDeclaration) {
MethodDeclaration md = (MethodDeclaration) parent.get();
+ if (md.returnType != null && Arrays.equals(md.returnType.getLastToken(), TypeReference.VOID)) {
+ job.checkReturnValue = false;
+ } else {
+ job.checkReturnValue = hasCheckReturnValueImplicatingAnnotations(parent, md.annotations);
+ }
job.parentType = parent.up();
job.isStatic = md.isStatic();
@@ -885,10 +893,11 @@ public class HandleBuilder extends EclipseAnnotationHandler {
}
}
out.statements = statements.isEmpty() ? null : statements.toArray(new Statement[0]);
- if (job.checkerFramework.generateSideEffectFree()) {
+ if (job.checkReturnValue && job.checkerFramework.generateSideEffectFree()) {
out.annotations = new Annotation[] {generateNamedAnnotation(job.source, CheckerFrameworkVersion.NAME__SIDE_EFFECT_FREE)};
}
out.receiver = generateBuildReceiver(job);
+ if (job.checkReturnValue) out.annotations = addCheckReturnValue(job.builderType, job.source, out.annotations);
if (staticName == null) createRelevantNonNullAnnotation(job.builderType, out);
out.traverse(new SetGeneratedByVisitor(job.source), (ClassScope) null);
return out;
diff --git a/src/core/lombok/eclipse/handlers/HandleConstructor.java b/src/core/lombok/eclipse/handlers/HandleConstructor.java
index 19415696..9229fbe7 100755
--- a/src/core/lombok/eclipse/handlers/HandleConstructor.java
+++ b/src/core/lombok/eclipse/handlers/HandleConstructor.java
@@ -475,9 +475,12 @@ public class HandleConstructor {
Annotation[] constructorProperties = null;
if (addConstructorProperties && !isLocalType(type)) constructorProperties = createConstructorProperties(source, fieldsToParam);
+ Annotation[] checkerFramework = null;
+ if (getCheckerFrameworkVersion(type).generateSideEffectFree()) checkerFramework = new Annotation[] { generateNamedAnnotation(source, CheckerFrameworkVersion.NAME__SIDE_EFFECT_FREE) };
+
constructor.annotations = copyAnnotations(source,
onConstructor.toArray(new Annotation[0]),
- constructorProperties);
+ constructorProperties, checkerFramework);
}
constructor.traverse(new SetGeneratedByVisitor(source), typeDeclaration.scope);
@@ -570,7 +573,10 @@ public class HandleConstructor {
assigns.add(nameRef);
Argument parameter = new Argument(field.name, fieldPos, copyType(field.type, source), Modifier.FINAL);
- parameter.annotations = copyAnnotations(source, findCopyableAnnotations(fieldNode));
+ Annotation[] checkerFramework = null;
+ if (getCheckerFrameworkVersion(type).generateSideEffectFree()) checkerFramework = new Annotation[] { generateNamedAnnotation(source, CheckerFrameworkVersion.NAME__SIDE_EFFECT_FREE) };
+
+ parameter.annotations = copyAnnotations(source, findCopyableAnnotations(fieldNode), checkerFramework);
if (parameter.annotations != null) {
parameter.bits |= Eclipse.HasTypeAnnotations;
constructor.bits |= Eclipse.HasTypeAnnotations;
diff --git a/src/core/lombok/eclipse/handlers/HandleEqualsAndHashCode.java b/src/core/lombok/eclipse/handlers/HandleEqualsAndHashCode.java
index b0e0e526..5ed93c09 100755
--- a/src/core/lombok/eclipse/handlers/HandleEqualsAndHashCode.java
+++ b/src/core/lombok/eclipse/handlers/HandleEqualsAndHashCode.java
@@ -272,6 +272,8 @@ public class HandleEqualsAndHashCode extends EclipseAnnotationHandler {
findDelegatesAndMarkAsHandled(fieldNode),
checkerFramework,
deprecated);
+ method.annotations = EclipseHandlerUtil.addCheckReturnValue(fieldNode, method, method.annotations);
}
if (addSuppressWarningsUnchecked) {
diff --git a/src/core/lombok/eclipse/handlers/HandleSetter.java b/src/core/lombok/eclipse/handlers/HandleSetter.java
index 662c6d4e..b0730d39 100644
--- a/src/core/lombok/eclipse/handlers/HandleSetter.java
+++ b/src/core/lombok/eclipse/handlers/HandleSetter.java
@@ -27,7 +27,6 @@ import static lombok.eclipse.handlers.EclipseHandlerUtil.*;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collection;
import java.util.List;
@@ -163,7 +162,7 @@ public class HandleSetter extends EclipseAnnotationHandler {
AnnotationValues accessors = getAccessorsForField(fieldNode);
String setterName = toSetterName(fieldNode, isBoolean, accessors);
boolean shouldReturnThis = shouldReturnThis(fieldNode, accessors);
- boolean fluent = accessors.isExplicit("fluent");
+ boolean fluent = accessors.isExplicit("fluent");
if (setterName == null) {
fieldNode.addWarning("Not generating setter for this field: It does not fit your @Accessors prefix list.");
diff --git a/src/core/lombok/eclipse/handlers/HandleStandardException.java b/src/core/lombok/eclipse/handlers/HandleStandardException.java
index 4672555c..e93c364b 100755
--- a/src/core/lombok/eclipse/handlers/HandleStandardException.java
+++ b/src/core/lombok/eclipse/handlers/HandleStandardException.java
@@ -25,6 +25,7 @@ import lombok.AccessLevel;
import lombok.ConfigurationKeys;
import lombok.experimental.StandardException;
import lombok.core.AST.Kind;
+import lombok.core.configuration.CheckerFrameworkVersion;
import lombok.core.AnnotationValues;
import lombok.eclipse.EclipseAnnotationHandler;
import lombok.eclipse.EclipseNode;
@@ -75,6 +76,9 @@ public class HandleStandardException extends EclipseAnnotationHandler {
out.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG;
out.returnType = new SingleTypeReference(classGenericName.toCharArray(), 0);
Annotation overrideAnn = override ? makeMarkerAnnotation(TypeConstants.JAVA_LANG_OVERRIDE, job.source) : null;
- Annotation sefAnn = job.checkerFramework.generateSideEffectFree() ? generateNamedAnnotation(job.source, CheckerFrameworkVersion.NAME__SIDE_EFFECT_FREE): null;
+ Annotation sefAnn = job.checkerFramework.generateSideEffectFree() ? generateNamedAnnotation(job.source, CheckerFrameworkVersion.NAME__SIDE_EFFECT_FREE) : null;
if (overrideAnn != null && sefAnn != null) out.annotations = new Annotation[] {overrideAnn, sefAnn};
else if (overrideAnn != null) out.annotations = new Annotation[] {overrideAnn};
else if (sefAnn != null) out.annotations = new Annotation[] {sefAnn};
out.receiver = HandleBuilder.generateBuildReceiver(job);
+ out.annotations = addCheckReturnValue(job.builderType, job.source, out.annotations);
out.traverse(new SetGeneratedByVisitor(job.source), (ClassScope) null);
return out;
}
@@ -893,6 +894,7 @@ public class HandleSuperBuilder extends EclipseAnnotationHandler {
statements.add(new ReturnStatement(allocationStatement, 0, 0));
out.statements = statements.isEmpty() ? null : statements.toArray(new Statement[0]);
out.receiver = HandleBuilder.generateBuildReceiver(job);
+ out.annotations = addCheckReturnValue(job.builderType, job.source, out.annotations);
createRelevantNonNullAnnotation(job.builderType, out);
out.traverse(new SetGeneratedByVisitor(job.source), (ClassScope) null);
return out;
diff --git a/src/core/lombok/eclipse/handlers/HandleToString.java b/src/core/lombok/eclipse/handlers/HandleToString.java
index b22d162f..72043264 100644
--- a/src/core/lombok/eclipse/handlers/HandleToString.java
+++ b/src/core/lombok/eclipse/handlers/HandleToString.java
@@ -298,6 +298,8 @@ public class HandleToString extends EclipseAnnotationHandler {
} else {
method.annotations = new Annotation[] { overrideAnnotation };
}
+ method.annotations = EclipseHandlerUtil.addCheckReturnValue(type, method, method.annotations);
+
method.arguments = null;
method.selector = "toString".toCharArray();
method.thrownExceptions = null;
diff --git a/src/core/lombok/eclipse/handlers/HandleWith.java b/src/core/lombok/eclipse/handlers/HandleWith.java
index 153f0c4a..9a657b2c 100644
--- a/src/core/lombok/eclipse/handlers/HandleWith.java
+++ b/src/core/lombok/eclipse/handlers/HandleWith.java
@@ -292,6 +292,7 @@ public class HandleWith extends EclipseAnnotationHandler {
param.annotations = copyAnnotations(source, copyableAnnotations, onParam.toArray(new Annotation[0]));
EclipseHandlerUtil.createRelevantNonNullAnnotation(fieldNode, method);
+ method.annotations = EclipseHandlerUtil.addCheckReturnValue(fieldNode, source, method.annotations);
method.traverse(new SetGeneratedByVisitor(source), parent.scope);
copyJavadoc(fieldNode, method, CopyJavadoc.WITH);
diff --git a/src/core/lombok/eclipse/handlers/HandleWithBy.java b/src/core/lombok/eclipse/handlers/HandleWithBy.java
index bfba91d4..27617827 100644
--- a/src/core/lombok/eclipse/handlers/HandleWithBy.java
+++ b/src/core/lombok/eclipse/handlers/HandleWithBy.java
@@ -375,6 +375,7 @@ public class HandleWithBy extends EclipseAnnotationHandler {
createRelevantNonNullAnnotation(sourceNode, param, method);
createRelevantNonNullAnnotation(fieldNode, method);
+ method.annotations = addCheckReturnValue(fieldNode, source, method.annotations);
method.traverse(new SetGeneratedByVisitor(source), parent.scope);
copyJavadoc(fieldNode, method, CopyJavadoc.WITH_BY);
diff --git a/src/core/lombok/javac/handlers/HandleBuilder.java b/src/core/lombok/javac/handlers/HandleBuilder.java
index 1e023f20..595695cd 100644
--- a/src/core/lombok/javac/handlers/HandleBuilder.java
+++ b/src/core/lombok/javac/handlers/HandleBuilder.java
@@ -29,6 +29,7 @@ import static lombok.javac.handlers.JavacHandlerUtil.*;
import java.util.ArrayList;
import javax.lang.model.element.Modifier;
+import javax.lang.model.type.TypeKind;
import com.sun.tools.javac.code.Flags;
import com.sun.tools.javac.tree.JCTree;
@@ -105,6 +106,7 @@ public class HandleBuilder extends JavacAnnotationHandler {
static class BuilderJob {
CheckerFrameworkVersion checkerFramework;
JavacNode parentType;
+ boolean checkReturnValue;
String builderMethodName, buildMethodName;
boolean isStatic;
List typeParams;
@@ -247,6 +249,7 @@ public class HandleBuilder extends JavacAnnotationHandler {
}
job.parentType = parent;
+ job.checkReturnValue = true;
JCClassDecl td = (JCClassDecl) parent.get();
ListBuffer allFields = new ListBuffer();
@@ -315,6 +318,7 @@ public class HandleBuilder extends JavacAnnotationHandler {
}
job.parentType = parent.up();
+ job.checkReturnValue = true;
JCClassDecl td = (JCClassDecl) job.parentType.get();
job.typeParams = job.builderTypeParams = td.typarams;
buildMethodReturnType = job.createBuilderParentTypeReference();
@@ -326,6 +330,11 @@ public class HandleBuilder extends JavacAnnotationHandler {
job.parentType = parent.up();
JCClassDecl td = (JCClassDecl) job.parentType.get();
JCMethodDecl jmd = (JCMethodDecl) fillParametersFrom.get();
+ if (jmd.restype instanceof JCPrimitiveTypeTree && ((JCPrimitiveTypeTree) jmd.restype).getPrimitiveTypeKind() == TypeKind.VOID) {
+ job.checkReturnValue = false;
+ } else {
+ job.checkReturnValue = hasCheckReturnValueImplicatingAnnotations(fillParametersFrom, jmd.mods.annotations);
+ }
job.isStatic = (jmd.mods.flags & Flags.STATIC) != 0;
JCExpression fullReturnType = jmd.restype;
@@ -338,7 +347,7 @@ public class HandleBuilder extends JavacAnnotationHandler {
}
if (job.builderClassName.indexOf('*') > -1) {
String replStr = returnTypeToBuilderClassName(annotationNode, td, buildMethodReturnType, job.typeParams);
- if (replStr == null) return; // shuold not happen
+ if (replStr == null) return; // should not happen
job.builderClassName = job.builderClassName.replace("*", replStr);
}
if (job.toBuilder) {
@@ -792,7 +801,7 @@ public class HandleBuilder extends JavacAnnotationHandler {
JCBlock body = maker.Block(0, statements.toList());
- List annsOnMethod = job.checkerFramework.generateSideEffectFree() ? List.of(maker.Annotation(genTypeRef(job.builderType, CheckerFrameworkVersion.NAME__SIDE_EFFECT_FREE), List.nil())) : List.nil();
+ List annsOnMethod = (job.checkReturnValue && job.checkerFramework.generateSideEffectFree()) ? List.of(maker.Annotation(genTypeRef(job.builderType, CheckerFrameworkVersion.NAME__SIDE_EFFECT_FREE), List.nil())) : List.nil();
JCVariableDecl recv = generateReceiver(job);
JCMethodDecl methodDef;
JCExpression returnTypeCopy = cloneType(maker, returnType, job.sourceNode);
@@ -801,6 +810,7 @@ public class HandleBuilder extends JavacAnnotationHandler {
} else {
methodDef = maker.MethodDef(maker.Modifiers(toJavacModifier(job.accessInners), annsOnMethod), job.toName(job.buildMethodName), returnTypeCopy, List.nil(), List.nil(), thrownExceptions, body, null);
}
+ if (job.checkReturnValue) addCheckReturnValue(methodDef.mods, job.builderType, job.sourceNode);
if (staticName == null) createRelevantNonNullAnnotation(job.builderType, methodDef);
return methodDef;
}
diff --git a/src/core/lombok/javac/handlers/HandleConstructor.java b/src/core/lombok/javac/handlers/HandleConstructor.java
index ca885cd6..046d51ad 100644
--- a/src/core/lombok/javac/handlers/HandleConstructor.java
+++ b/src/core/lombok/javac/handlers/HandleConstructor.java
@@ -360,7 +360,12 @@ public class HandleConstructor {
assigns.append(maker.Exec(assign));
}
- JCModifiers mods = maker.Modifiers(toJavacModifier(level), List.nil());
+ CheckerFrameworkVersion checkerFramework = getCheckerFrameworkVersion(source);
+ List anns = checkerFramework.generateSideEffectFree() ?
+ List.of(maker.Annotation(genTypeRef(source, CheckerFrameworkVersion.NAME__SIDE_EFFECT_FREE), List.nil())) :
+ List.nil();
+
+ JCModifiers mods = maker.Modifiers(toJavacModifier(level), anns);
if (addConstructorProperties && !isLocalType(typeNode) && LombokOptionsFactory.getDelombokOptions(typeNode.getContext()).getFormatPreferences().generateConstructorProperties()) {
addConstructorProperties(mods, typeNode, fieldsToParam);
}
@@ -442,8 +447,6 @@ public class HandleConstructor {
JavacTreeMaker maker = typeNode.getTreeMaker();
JCClassDecl type = (JCClassDecl) typeNode.get();
- JCModifiers mods = maker.Modifiers(Flags.STATIC | toJavacModifier(level));
-
JCExpression returnType, constructorType;
ListBuffer typeParams = new ListBuffer();
@@ -473,6 +476,13 @@ public class HandleConstructor {
JCReturn returnStatement = maker.Return(maker.NewClass(null, List.nil(), constructorType, args.toList(), null));
JCBlock body = maker.Block(0, List.of(returnStatement));
+ CheckerFrameworkVersion checkerFramework = getCheckerFrameworkVersion(source);
+ List anns = checkerFramework.generateSideEffectFree() ?
+ List.of(maker.Annotation(genTypeRef(source, CheckerFrameworkVersion.NAME__SIDE_EFFECT_FREE), List.nil())) :
+ List.nil();
+
+ JCModifiers mods = maker.Modifiers(Flags.STATIC | toJavacModifier(level), anns);
+
JCMethodDecl methodDef = maker.MethodDef(mods, typeNode.toName(name), returnType, typeParams.toList(), params.toList(), List.nil(), body, null);
createRelevantNonNullAnnotation(typeNode, methodDef);
return recursiveSetGeneratedBy(methodDef, source);
diff --git a/src/core/lombok/javac/handlers/HandleEqualsAndHashCode.java b/src/core/lombok/javac/handlers/HandleEqualsAndHashCode.java
index 07610a0b..50818201 100644
--- a/src/core/lombok/javac/handlers/HandleEqualsAndHashCode.java
+++ b/src/core/lombok/javac/handlers/HandleEqualsAndHashCode.java
@@ -228,6 +228,7 @@ public class HandleEqualsAndHashCode extends JavacAnnotationHandlernil()));
}
JCModifiers mods = maker.Modifiers(Flags.PUBLIC, annsOnMethod);
+ addCheckReturnValue(mods, typeNode, source);
JCExpression returnType = maker.TypeIdent(CTC_INT);
ListBuffer statements = new ListBuffer();
@@ -439,6 +440,7 @@ public class HandleEqualsAndHashCode extends JavacAnnotationHandlernil()));
}
JCModifiers mods = maker.Modifiers(Flags.PUBLIC, annsOnMethod);
+ addCheckReturnValue(mods, typeNode, source);
JCExpression objectType;
if (annsOnParamOnMethod.isEmpty()) {
objectType = genJavaLangTypeRef(typeNode, "Object");
diff --git a/src/core/lombok/javac/handlers/HandleGetter.java b/src/core/lombok/javac/handlers/HandleGetter.java
index f6b28ffb..ce29cb3c 100644
--- a/src/core/lombok/javac/handlers/HandleGetter.java
+++ b/src/core/lombok/javac/handlers/HandleGetter.java
@@ -56,6 +56,7 @@ import com.sun.tools.javac.tree.JCTree.JCExpressionStatement;
import com.sun.tools.javac.tree.JCTree.JCIf;
import com.sun.tools.javac.tree.JCTree.JCMethodDecl;
import com.sun.tools.javac.tree.JCTree.JCMethodInvocation;
+import com.sun.tools.javac.tree.JCTree.JCModifiers;
import com.sun.tools.javac.tree.JCTree.JCPrimitiveTypeTree;
import com.sun.tools.javac.tree.JCTree.JCStatement;
import com.sun.tools.javac.tree.JCTree.JCSynchronized;
@@ -264,9 +265,11 @@ public class HandleGetter extends JavacAnnotationHandler {
if (getCheckerFrameworkVersion(field).generateSideEffectFree()) annsOnMethod = annsOnMethod.prepend(treeMaker.Annotation(genTypeRef(field, CheckerFrameworkVersion.NAME__SIDE_EFFECT_FREE), List.nil()));
}
if (isFieldDeprecated(field)) annsOnMethod = annsOnMethod.prepend(treeMaker.Annotation(genJavaLangTypeRef(field, "Deprecated"), List.nil()));
+ JCModifiers mods = treeMaker.Modifiers(access, annsOnMethod);
+ addCheckReturnValue(mods, field, source);
if (makeFinal) access |= Flags.FINAL;
- JCMethodDecl decl = recursiveSetGeneratedBy(treeMaker.MethodDef(treeMaker.Modifiers(access, annsOnMethod), methodName, methodType,
+ JCMethodDecl decl = recursiveSetGeneratedBy(treeMaker.MethodDef(mods, methodName, methodType,
methodGenericParams, parameters, throwsClauses, methodBody, annotationMethodDefaultValue), source);
decl.mods.annotations = decl.mods.annotations.appendList(delegates);
diff --git a/src/core/lombok/javac/handlers/HandleStandardException.java b/src/core/lombok/javac/handlers/HandleStandardException.java
index 56bf8905..8b28c52e 100644
--- a/src/core/lombok/javac/handlers/HandleStandardException.java
+++ b/src/core/lombok/javac/handlers/HandleStandardException.java
@@ -31,6 +31,7 @@ import lombok.AccessLevel;
import lombok.ConfigurationKeys;
import lombok.experimental.StandardException;
import lombok.core.AST.Kind;
+import lombok.core.configuration.CheckerFrameworkVersion;
import lombok.core.AnnotationValues;
import lombok.delombok.LombokOptionsFactory;
import lombok.javac.Javac;
@@ -205,7 +206,12 @@ public class HandleStandardException extends JavacAnnotationHandlernil());
+ CheckerFrameworkVersion checkerFramework = getCheckerFrameworkVersion(source);
+ List anns = checkerFramework.generateSideEffectFree() ?
+ List.of(maker.Annotation(genTypeRef(source, CheckerFrameworkVersion.NAME__SIDE_EFFECT_FREE), List.nil())) :
+ List.nil();
+
+ JCModifiers mods = maker.Modifiers(toJavacModifier(level), anns);
if (addConstructorProperties) addConstructorProperties(mods, typeNode, msgParam, causeParam);
return recursiveSetGeneratedBy(maker.MethodDef(mods, typeNode.toName(""),
null, List.nil(), params.toList(), List.nil(),
diff --git a/src/core/lombok/javac/handlers/HandleSuperBuilder.java b/src/core/lombok/javac/handlers/HandleSuperBuilder.java
index c5a50508..fa0ba9dd 100644
--- a/src/core/lombok/javac/handlers/HandleSuperBuilder.java
+++ b/src/core/lombok/javac/handlers/HandleSuperBuilder.java
@@ -841,6 +841,7 @@ public class HandleSuperBuilder extends JavacAnnotationHandler {
} else {
methodDef = maker.MethodDef(modifiers, name, returnType, List.nil(), List.nil(), List.nil(), null, null);
}
+ addCheckReturnValue(methodDef.mods, job.builderType, job.sourceNode);
return methodDef;
}
@@ -869,6 +870,7 @@ public class HandleSuperBuilder extends JavacAnnotationHandler {
} else {
methodDef = maker.MethodDef(modifiers, job.toName(job.buildMethodName), cloneSelfType(job.parentType), List.nil(), List.nil(), thrownExceptions, body, null);
}
+ addCheckReturnValue(methodDef.mods, job.builderType, job.sourceNode);
createRelevantNonNullAnnotation(job.builderType, methodDef);
return methodDef;
}
diff --git a/src/core/lombok/javac/handlers/HandleToString.java b/src/core/lombok/javac/handlers/HandleToString.java
index 8eccf079..4737e9ac 100644
--- a/src/core/lombok/javac/handlers/HandleToString.java
+++ b/src/core/lombok/javac/handlers/HandleToString.java
@@ -153,6 +153,7 @@ public class HandleToString extends JavacAnnotationHandler {
List annsOnMethod = List.of(overrideAnnotation);
if (getCheckerFrameworkVersion(typeNode).generateSideEffectFree()) annsOnMethod = annsOnMethod.prepend(maker.Annotation(genTypeRef(typeNode, CheckerFrameworkVersion.NAME__SIDE_EFFECT_FREE), List.nil()));
JCModifiers mods = maker.Modifiers(Flags.PUBLIC, annsOnMethod);
+ addCheckReturnValue(mods, typeNode, source);
JCExpression returnType = genJavaLangTypeRef(typeNode, "String");
boolean first = true;
diff --git a/src/core/lombok/javac/handlers/HandleWith.java b/src/core/lombok/javac/handlers/HandleWith.java
index 52f2c192..bd80b351 100644
--- a/src/core/lombok/javac/handlers/HandleWith.java
+++ b/src/core/lombok/javac/handlers/HandleWith.java
@@ -290,6 +290,7 @@ public class HandleWith extends JavacAnnotationHandler {
if (makeFinal) access |= Flags.FINAL;
JCMethodDecl decl = recursiveSetGeneratedBy(maker.MethodDef(maker.Modifiers(access, annsOnMethod), methodName, returnType,
methodGenericParams, parameters, throwsClauses, methodBody, annotationMethodDefaultValue), source);
+ addCheckReturnValue(decl.mods, field, source);
copyJavadoc(field, decl, CopyJavadoc.WITH);
return decl;
}
diff --git a/src/core/lombok/javac/handlers/HandleWithBy.java b/src/core/lombok/javac/handlers/HandleWithBy.java
index 7978928f..aad502a0 100644
--- a/src/core/lombok/javac/handlers/HandleWithBy.java
+++ b/src/core/lombok/javac/handlers/HandleWithBy.java
@@ -334,6 +334,7 @@ public class HandleWithBy extends JavacAnnotationHandler {
createRelevantNonNullAnnotation(source, param);
JCMethodDecl decl = recursiveSetGeneratedBy(maker.MethodDef(maker.Modifiers(access, annsOnMethod), methodName, returnType,
methodGenericParams, parameters, throwsClauses, methodBody, annotationMethodDefaultValue), source);
+ addCheckReturnValue(decl.mods, field, source);
copyJavadoc(field, decl, CopyJavadoc.WITH_BY);
createRelevantNonNullAnnotation(source, decl);
return decl;
diff --git a/src/core/lombok/javac/handlers/JavacHandlerUtil.java b/src/core/lombok/javac/handlers/JavacHandlerUtil.java
index 0c075295..f2ac8e4d 100644
--- a/src/core/lombok/javac/handlers/JavacHandlerUtil.java
+++ b/src/core/lombok/javac/handlers/JavacHandlerUtil.java
@@ -96,6 +96,7 @@ import lombok.core.AnnotationValues.AnnotationValue;
import lombok.core.CleanupTask;
import lombok.core.LombokImmutableList;
import lombok.core.TypeResolver;
+import lombok.core.configuration.CheckReturnValueFlavor;
import lombok.core.configuration.CheckerFrameworkVersion;
import lombok.core.configuration.NullAnnotationLibrary;
import lombok.core.configuration.NullCheckExceptionType;
@@ -1554,6 +1555,13 @@ public class JavacHandlerUtil {
}
}
+ public static void addCheckReturnValue(JCModifiers mods, JavacNode node, JavacNode source) {
+ java.util.List flavors = node.getAst().readConfiguration(ConfigurationKeys.CHECK_RETURN_VALUE_ANNOTATION);
+ for (CheckReturnValueFlavor flavor : flavors) {
+ addAnnotation(mods, node, source, flavor.getMarkerAnnotationFullyQualifiedName(), null);
+ }
+ }
+
public static void addAnnotation(JCModifiers mods, JavacNode node, JavacNode source, String annotationTypeFqn, JCExpression arg) {
boolean isJavaLangBased;
String simpleName; {
@@ -1730,6 +1738,16 @@ public class JavacHandlerUtil {
return false;
}
+ public static boolean hasCheckReturnValueImplicatingAnnotations(JavacNode node, List anns) {
+ if (anns == null) return false;
+ for (JCAnnotation ann : anns) {
+ String annotationTypeName = getTypeName(ann.annotationType);
+ for (String nn : IMPLIES_CHECK_RETURN_VALUE_ANNOTATIONS) if (typeMatches(nn, node, annotationTypeName)) return true;
+ }
+
+ return false;
+ }
+
/**
* Searches the given field node for annotations and returns each one that is 'copyable' (either via configuration or from the base list).
*/
diff --git a/test/transform/resource/after-delombok/CheckReturnValueBuilder.java b/test/transform/resource/after-delombok/CheckReturnValueBuilder.java
new file mode 100644
index 00000000..f8288793
--- /dev/null
+++ b/test/transform/resource/after-delombok/CheckReturnValueBuilder.java
@@ -0,0 +1,60 @@
+class CheckReturnValueBuilder {
+ private final int x;
+ private final String name;
+ @java.lang.SuppressWarnings("all")
+ @lombok.Generated
+ CheckReturnValueBuilder(final int x, final String name) {
+ this.x = x;
+ this.name = name;
+ }
+ @java.lang.SuppressWarnings("all")
+ @lombok.Generated
+ public static class CheckReturnValueBuilderBuilder {
+ @java.lang.SuppressWarnings("all")
+ @lombok.Generated
+ private int x;
+ @java.lang.SuppressWarnings("all")
+ @lombok.Generated
+ private String name;
+ @java.lang.SuppressWarnings("all")
+ @lombok.Generated
+ CheckReturnValueBuilderBuilder() {
+ }
+ /**
+ * @return {@code this}.
+ */
+ @java.lang.SuppressWarnings("all")
+ @lombok.Generated
+ public CheckReturnValueBuilder.CheckReturnValueBuilderBuilder x(final int x) {
+ this.x = x;
+ return this;
+ }
+ /**
+ * @return {@code this}.
+ */
+ @java.lang.SuppressWarnings("all")
+ @lombok.Generated
+ public CheckReturnValueBuilder.CheckReturnValueBuilderBuilder name(final String name) {
+ this.name = name;
+ return this;
+ }
+ @lombok.CheckReturnValue
+ @java.lang.SuppressWarnings("all")
+ @lombok.Generated
+ public CheckReturnValueBuilder build() {
+ return new CheckReturnValueBuilder(this.x, this.name);
+ }
+ @java.lang.Override
+ @lombok.CheckReturnValue
+ @java.lang.SuppressWarnings("all")
+ @lombok.Generated
+ public java.lang.String toString() {
+ return "CheckReturnValueBuilder.CheckReturnValueBuilderBuilder(x=" + this.x + ", name=" + this.name + ")";
+ }
+ }
+ @java.lang.SuppressWarnings("all")
+ @lombok.Generated
+ public static CheckReturnValueBuilder.CheckReturnValueBuilderBuilder builder() {
+ return new CheckReturnValueBuilder.CheckReturnValueBuilderBuilder();
+ }
+}
diff --git a/test/transform/resource/after-delombok/CheckReturnValueWith.java b/test/transform/resource/after-delombok/CheckReturnValueWith.java
new file mode 100644
index 00000000..a679b4b2
--- /dev/null
+++ b/test/transform/resource/after-delombok/CheckReturnValueWith.java
@@ -0,0 +1,26 @@
+class CheckReturnValueWith {
+ final int x;
+ final String name;
+ CheckReturnValueWith(int x, String name) {
+ this.x = x;
+ this.name = name;
+ }
+ /**
+ * @return a clone of this object, except with this updated property (returns {@code this} if an identical value is passed).
+ */
+ @lombok.CheckReturnValue
+ @java.lang.SuppressWarnings("all")
+ @lombok.Generated
+ public CheckReturnValueWith withX(final int x) {
+ return this.x == x ? this : new CheckReturnValueWith(x, this.name);
+ }
+ /**
+ * @return a clone of this object, except with this updated property (returns {@code this} if an identical value is passed).
+ */
+ @lombok.CheckReturnValue
+ @java.lang.SuppressWarnings("all")
+ @lombok.Generated
+ public CheckReturnValueWith withName(final String name) {
+ return this.name == name ? this : new CheckReturnValueWith(this.x, name);
+ }
+}
diff --git a/test/transform/resource/after-delombok/CheckerFrameworkBasic.java b/test/transform/resource/after-delombok/CheckerFrameworkBasic.java
index 62c8b0c7..dd59b9eb 100644
--- a/test/transform/resource/after-delombok/CheckerFrameworkBasic.java
+++ b/test/transform/resource/after-delombok/CheckerFrameworkBasic.java
@@ -70,6 +70,7 @@ class CheckerFrameworkBasic {
public java.lang.String toString() {
return "CheckerFrameworkBasic(x=" + this.getX() + ", y=" + this.getY() + ", z=" + this.getZ() + ")";
}
+ @org.checkerframework.dataflow.qual.SideEffectFree
@java.lang.SuppressWarnings("all")
@lombok.Generated
public CheckerFrameworkBasic(final int x, final int y, final int z) {
diff --git a/test/transform/resource/after-delombok/CheckerFrameworkBuilder.java b/test/transform/resource/after-delombok/CheckerFrameworkBuilder.java
index 5c31e346..c19bf5cf 100644
--- a/test/transform/resource/after-delombok/CheckerFrameworkBuilder.java
+++ b/test/transform/resource/after-delombok/CheckerFrameworkBuilder.java
@@ -11,6 +11,7 @@ class CheckerFrameworkBuilder {
private static int $default$x() {
return 5;
}
+ @org.checkerframework.dataflow.qual.SideEffectFree
@java.lang.SuppressWarnings("all")
@lombok.Generated
CheckerFrameworkBuilder(final int x, final int y, final int z, final List names) {
@@ -37,6 +38,7 @@ class CheckerFrameworkBuilder {
@java.lang.SuppressWarnings("all")
@lombok.Generated
private java.util.ArrayList names;
+ @org.checkerframework.dataflow.qual.SideEffectFree
@java.lang.SuppressWarnings("all")
@lombok.Generated
CheckerFrameworkBuilderBuilder() {
diff --git a/test/transform/resource/after-delombok/CheckerFrameworkSuperBuilder.java b/test/transform/resource/after-delombok/CheckerFrameworkSuperBuilder.java
index 000b2816..8e62a8b7 100644
--- a/test/transform/resource/after-delombok/CheckerFrameworkSuperBuilder.java
+++ b/test/transform/resource/after-delombok/CheckerFrameworkSuperBuilder.java
@@ -100,6 +100,7 @@ class CheckerFrameworkSuperBuilder {
@java.lang.SuppressWarnings("all")
@lombok.Generated
private static final class ParentBuilderImpl extends CheckerFrameworkSuperBuilder.Parent.ParentBuilder {
+ @org.checkerframework.dataflow.qual.SideEffectFree
@java.lang.SuppressWarnings("all")
@lombok.Generated
private ParentBuilderImpl() {
@@ -207,6 +208,7 @@ class CheckerFrameworkSuperBuilder {
@java.lang.SuppressWarnings("all")
@lombok.Generated
private static final class ZChildBuilderImpl extends CheckerFrameworkSuperBuilder.ZChild.ZChildBuilder {
+ @org.checkerframework.dataflow.qual.SideEffectFree
@java.lang.SuppressWarnings("all")
@lombok.Generated
private ZChildBuilderImpl() {
diff --git a/test/transform/resource/after-ecj/CheckReturnValueBuilder.java b/test/transform/resource/after-ecj/CheckReturnValueBuilder.java
new file mode 100644
index 00000000..fa85fea1
--- /dev/null
+++ b/test/transform/resource/after-ecj/CheckReturnValueBuilder.java
@@ -0,0 +1,39 @@
+@lombok.Builder class CheckReturnValueBuilder {
+ public static @java.lang.SuppressWarnings("all") @lombok.Generated class CheckReturnValueBuilderBuilder {
+ private @java.lang.SuppressWarnings("all") @lombok.Generated int x;
+ private @java.lang.SuppressWarnings("all") @lombok.Generated String name;
+ @java.lang.SuppressWarnings("all") @lombok.Generated CheckReturnValueBuilderBuilder() {
+ super();
+ }
+ /**
+ * @return {@code this}.
+ */
+ public @java.lang.SuppressWarnings("all") @lombok.Generated CheckReturnValueBuilder.CheckReturnValueBuilderBuilder x(final int x) {
+ this.x = x;
+ return this;
+ }
+ /**
+ * @return {@code this}.
+ */
+ public @java.lang.SuppressWarnings("all") @lombok.Generated CheckReturnValueBuilder.CheckReturnValueBuilderBuilder name(final String name) {
+ this.name = name;
+ return this;
+ }
+ public @lombok.CheckReturnValue @java.lang.SuppressWarnings("all") @lombok.Generated CheckReturnValueBuilder build() {
+ return new CheckReturnValueBuilder(this.x, this.name);
+ }
+ public @java.lang.Override @lombok.CheckReturnValue @java.lang.SuppressWarnings("all") @lombok.Generated java.lang.String toString() {
+ return (((("CheckReturnValueBuilder.CheckReturnValueBuilderBuilder(x=" + this.x) + ", name=") + this.name) + ")");
+ }
+ }
+ private final int x;
+ private final String name;
+ @java.lang.SuppressWarnings("all") @lombok.Generated CheckReturnValueBuilder(final int x, final String name) {
+ super();
+ this.x = x;
+ this.name = name;
+ }
+ public static @java.lang.SuppressWarnings("all") @lombok.Generated CheckReturnValueBuilder.CheckReturnValueBuilderBuilder builder() {
+ return new CheckReturnValueBuilder.CheckReturnValueBuilderBuilder();
+ }
+}
diff --git a/test/transform/resource/after-ecj/CheckReturnValueWith.java b/test/transform/resource/after-ecj/CheckReturnValueWith.java
new file mode 100644
index 00000000..c798870a
--- /dev/null
+++ b/test/transform/resource/after-ecj/CheckReturnValueWith.java
@@ -0,0 +1,22 @@
+import lombok.With;
+class CheckReturnValueWith {
+ final @With int x;
+ final @With String name;
+ CheckReturnValueWith(int x, String name) {
+ super();
+ this.x = x;
+ this.name = name;
+ }
+ /**
+ * @return a clone of this object, except with this updated property (returns {@code this} if an identical value is passed).
+ */
+ public @lombok.CheckReturnValue @java.lang.SuppressWarnings("all") @lombok.Generated CheckReturnValueWith withX(final int x) {
+ return ((this.x == x) ? this : new CheckReturnValueWith(x, this.name));
+ }
+ /**
+ * @return a clone of this object, except with this updated property (returns {@code this} if an identical value is passed).
+ */
+ public @lombok.CheckReturnValue @java.lang.SuppressWarnings("all") @lombok.Generated CheckReturnValueWith withName(final String name) {
+ return ((this.name == name) ? this : new CheckReturnValueWith(this.x, name));
+ }
+}
diff --git a/test/transform/resource/after-ecj/CheckerFrameworkBasic.java b/test/transform/resource/after-ecj/CheckerFrameworkBasic.java
index a52e0c2c..8687d8f2 100644
--- a/test/transform/resource/after-ecj/CheckerFrameworkBasic.java
+++ b/test/transform/resource/after-ecj/CheckerFrameworkBasic.java
@@ -58,7 +58,7 @@ import lombok.With;
public @java.lang.Override @org.checkerframework.dataflow.qual.SideEffectFree @java.lang.SuppressWarnings("all") @lombok.Generated java.lang.String toString() {
return (((((("CheckerFrameworkBasic(x=" + this.getX()) + ", y=") + this.getY()) + ", z=") + this.getZ()) + ")");
}
- public @java.lang.SuppressWarnings("all") @lombok.Generated CheckerFrameworkBasic(final int x, final int y, final int z) {
+ public @org.checkerframework.dataflow.qual.SideEffectFree @java.lang.SuppressWarnings("all") @lombok.Generated CheckerFrameworkBasic(final int x, final int y, final int z) {
super();
this.x = x;
this.y = y;
diff --git a/test/transform/resource/after-ecj/CheckerFrameworkBuilder.java b/test/transform/resource/after-ecj/CheckerFrameworkBuilder.java
index 15872a12..1d40f35f 100644
--- a/test/transform/resource/after-ecj/CheckerFrameworkBuilder.java
+++ b/test/transform/resource/after-ecj/CheckerFrameworkBuilder.java
@@ -8,7 +8,7 @@ import lombok.Singular;
private @java.lang.SuppressWarnings("all") @lombok.Generated int y;
private @java.lang.SuppressWarnings("all") @lombok.Generated int z;
private @java.lang.SuppressWarnings("all") @lombok.Generated java.util.ArrayList names;
- @java.lang.SuppressWarnings("all") @lombok.Generated CheckerFrameworkBuilderBuilder() {
+ @org.checkerframework.dataflow.qual.SideEffectFree @java.lang.SuppressWarnings("all") @lombok.Generated CheckerFrameworkBuilderBuilder() {
super();
}
/**
@@ -82,7 +82,7 @@ import lombok.Singular;
private static @java.lang.SuppressWarnings("all") @lombok.Generated int $default$x() {
return 5;
}
- @java.lang.SuppressWarnings("all") @lombok.Generated CheckerFrameworkBuilder(final int x, final int y, final int z, final List names) {
+ @org.checkerframework.dataflow.qual.SideEffectFree @java.lang.SuppressWarnings("all") @lombok.Generated CheckerFrameworkBuilder(final int x, final int y, final int z, final List names) {
super();
this.x = x;
this.y = y;
diff --git a/test/transform/resource/before/CheckReturnValueBuilder.java b/test/transform/resource/before/CheckReturnValueBuilder.java
new file mode 100644
index 00000000..83c9e551
--- /dev/null
+++ b/test/transform/resource/before/CheckReturnValueBuilder.java
@@ -0,0 +1,6 @@
+//CONF: lombok.checkReturnValueAnnotation = lombok
+@lombok.Builder
+class CheckReturnValueBuilder {
+ private final int x;
+ private final String name;
+}
diff --git a/test/transform/resource/before/CheckReturnValueWith.java b/test/transform/resource/before/CheckReturnValueWith.java
new file mode 100644
index 00000000..6ed0dba9
--- /dev/null
+++ b/test/transform/resource/before/CheckReturnValueWith.java
@@ -0,0 +1,11 @@
+//CONF: lombok.checkReturnValueAnnotation = lombok
+import lombok.With;
+class CheckReturnValueWith {
+ @With final int x;
+ @With final String name;
+
+ CheckReturnValueWith(int x, String name) {
+ this.x = x;
+ this.name = name;
+ }
+}
diff --git a/website/templates/features/Builder.html b/website/templates/features/Builder.html
index 4fd90741..0449620f 100644
--- a/website/templates/features/Builder.html
+++ b/website/templates/features/Builder.html
@@ -234,6 +234,8 @@ public class JacksonExample {
protected keyword is different inside the inner class, and the precise behavior that PROTECTED would indicate (access by any source in the same package is allowed, as well as any subclasses from the outer class, marked with @Builder is not possible, and marking the inner members public is as close as we can get.
If you have configured a nullity annotation flavour via lombok.config key lombok.addNullAnnotations, any plural-form generated builder methods for @Singular marked properties (these plural form methods take a collection of some sort and add all elements) get a nullity annotation on the parameter. You get a non-null one normally, but if you have configured the behavior on null being passed in as collection to IGNORE, a nullable annotation is generated instead.
+
+ The generated build() method will be annotated to indicate that callers must not ignore the returned value if you ask lombok to do this via the lombok.checkReturnValueAnnotation configuration key. However, if applying @Builder to a method, lombok doesn't add such an annotation, unless the method you marked with @Builder is itself marked with any annotation that implies 'check return value', including annotations that indicate side effect free or pure. If @Builder is applied to an explicitly written constructor, the @CheckReturnValue annotation will be added to the build() method if requested, even though constructors can in theory have side effects, and thus, ignoring the return value could be acceptable. Nevertheless, lombok assumes all constructors have no effect other than making an object, and you cannot override this assumption.
@f.smallPrint>
@f.scaffold>
diff --git a/website/templates/features/SuperBuilder.html b/website/templates/features/SuperBuilder.html
index bc682468..0af65b85 100644
--- a/website/templates/features/SuperBuilder.html
+++ b/website/templates/features/SuperBuilder.html
@@ -86,6 +86,8 @@
For remarks on @Singular, see the @Builder documentation's small print.
Various well known annotations about nullity cause null checks to be inserted and will be copied to parameter of the builder's 'setter' method. See Getter/Setter documentation's small print for more information.
+
+ The generated build() method will be annotated to indicate that callers must not ignore the returned value if you ask lombok to do this via the lombok.checkReturnValueAnnotation configuration key. While in theory constructors can have side effects, lombok always assumes they do not, and you cannot override this assumption.
@f.smallPrint>
@f.scaffold>
diff --git a/website/templates/features/configuration.html b/website/templates/features/configuration.html
index 6675d61e..fc96e0f0 100644
--- a/website/templates/features/configuration.html
+++ b/website/templates/features/configuration.html
@@ -89,6 +89,15 @@
Many flavors are available: jspecify (recommended), checkerframework (recommended), jakarta, eclipse, jetbrains, netbeans, androidx, findbugs, spring, jml, javax (=JSR305; not recommended), android.support (deprecated within android), or define your own via CUSTOM:fully.qualified.NonNullAnnotation:fully.qualified.NullableAnnotation; if your nullity annotation is solely of the type use style (it annotates types, such as eclipse's and checkerframework's offerings, versus annotating methods and parameters), the format is CUSTOM:TYPE_USE:nonnullanno:nullableanno.
This feature was added in lombok v1.18.12.
+
+ Lombok can add annotations to methods it generates if code that invokes this method should always to something with the returned value. For example, withX methods, and @Builder's build() method.
+ Lombok ships with its own take on this annotation named @lombok.CheckReturnValue. Static analysis tools (IntelliJ, Error Prone, SpotBugs) will then warn when callers discard the result. By default, no such annotation is added. Enable with:
+
+ lombok.checkReturnValueAnnotation = <flavor>
+
+ Supported flavors are: lombok, as well as androidx which emits @androidx.annotation.CheckResult.
+ Default: none. A future lombok release may flip the default to lombok.
+ This feature was added in lombok v1.18.48.
Lombok can be configured to add @lombok.Generated annotations to all generated nodes where possible; useful for JaCoCo (which has built in support),
or other style checkers and code coverage tools: