diff --git a/src/core/lombok/CheckReturnValue.java b/src/core/lombok/CheckReturnValue.java index 0f6ae868..0736108f 100644 --- a/src/core/lombok/CheckReturnValue.java +++ b/src/core/lombok/CheckReturnValue.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2025 The Project Lombok Authors. + * 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 diff --git a/src/core/lombok/ConfigurationKeys.java b/src/core/lombok/ConfigurationKeys.java index 9dafc083..6ea02ebe 100644 --- a/src/core/lombok/ConfigurationKeys.java +++ b/src/core/lombok/ConfigurationKeys.java @@ -90,16 +90,22 @@ public class ConfigurationKeys { * If {@code true}, lombok generates {@code @lombok.Generated} on all fields, methods, and types that are generated. */ 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 none} | {@code lombok}]. + * lombok configuration: {@code lombok.checkReturnValueAnnotation} += {@code lombok} / {@code androidx}. * - * If set to {@code lombok}, lombok generates {@code @lombok.CheckReturnValue} on generated methods where the return value should not be ignored, - * such as {@code @With} methods and {@code @Builder}'s {@code build()} method. - * If set to {@code none} (the current default), no such annotation is emitted. A future lombok release may flip the default to {@code lombok}. + * 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 to emit on generated methods. Values: none, lombok (default: none).") {}; - + 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 index b313ee73..15b1225b 100644 --- a/src/core/lombok/core/configuration/CheckReturnValueFlavor.java +++ b/src/core/lombok/core/configuration/CheckReturnValueFlavor.java @@ -21,7 +21,23 @@ */ package lombok.core.configuration; -/** Used for lombok configuration to select which {@code @CheckReturnValue} annotation flavor to emit on generated methods. */ -public enum CheckReturnValueFlavor { - NONE, LOMBOK; +/** 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/eclipse/handlers/EclipseHandlerUtil.java b/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java index a8383898..68763853 100644 --- a/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java +++ b/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java @@ -2108,7 +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 char[][] LOMBOK_CHECK_RETURN_VALUE = Eclipse.fromQualifiedName("lombok.CheckReturnValue"); + private static final Map checkReturnValueQualifiedNameCache = new HashMap(); public static Annotation[] addSuppressWarningsAll(EclipseNode node, ASTNode source, Annotation[] originalAnnotationArray) { Annotation[] anns = originalAnnotationArray; @@ -2140,17 +2140,17 @@ public class EclipseHandlerUtil { } public static Annotation[] addCheckReturnValue(EclipseNode node, ASTNode source, Annotation[] originalAnnotationArray) { - CheckReturnValueFlavor flavor = node.getAst().readConfiguration(ConfigurationKeys.CHECK_RETURN_VALUE_ANNOTATION); - if (flavor == null) flavor = CheckReturnValueFlavor.NONE; - switch (flavor) { - case LOMBOK: - return addAnnotation(source, originalAnnotationArray, LOMBOK_CHECK_RETURN_VALUE); - case NONE: - default: - return 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 750a6b21..51aa2f21 100755 --- a/src/core/lombok/eclipse/handlers/HandleBuilder.java +++ b/src/core/lombok/eclipse/handlers/HandleBuilder.java @@ -897,12 +897,12 @@ public class HandleBuilder extends EclipseAnnotationHandler { out.annotations = new Annotation[] {generateNamedAnnotation(job.source, CheckerFrameworkVersion.NAME__SIDE_EFFECT_FREE)}; } out.receiver = generateBuildReceiver(job); - out.annotations = addCheckReturnValue(job.builderType, job.source, out.annotations); + 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; } - + private TypeReference[] typeParameterNames(TypeParameter[] typeParameters) { if (typeParameters == null) return null; 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/HandleSuperBuilder.java b/src/core/lombok/eclipse/handlers/HandleSuperBuilder.java index c145b911..7aeb03a6 100644 --- a/src/core/lombok/eclipse/handlers/HandleSuperBuilder.java +++ b/src/core/lombok/eclipse/handlers/HandleSuperBuilder.java @@ -868,6 +868,7 @@ public class HandleSuperBuilder extends EclipseAnnotationHandler { 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; } 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 b286e769..9a657b2c 100644 --- a/src/core/lombok/eclipse/handlers/HandleWith.java +++ b/src/core/lombok/eclipse/handlers/HandleWith.java @@ -293,7 +293,7 @@ public class HandleWith extends EclipseAnnotationHandler { EclipseHandlerUtil.createRelevantNonNullAnnotation(fieldNode, method); method.annotations = EclipseHandlerUtil.addCheckReturnValue(fieldNode, source, method.annotations); - + method.traverse(new SetGeneratedByVisitor(source), parent.scope); copyJavadoc(fieldNode, method, CopyJavadoc.WITH); return method; diff --git a/src/core/lombok/eclipse/handlers/HandleWithBy.java b/src/core/lombok/eclipse/handlers/HandleWithBy.java index e25bb843..27617827 100644 --- a/src/core/lombok/eclipse/handlers/HandleWithBy.java +++ b/src/core/lombok/eclipse/handlers/HandleWithBy.java @@ -376,7 +376,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); return method; diff --git a/src/core/lombok/javac/handlers/HandleBuilder.java b/src/core/lombok/javac/handlers/HandleBuilder.java index 2b944f1d..595695cd 100644 --- a/src/core/lombok/javac/handlers/HandleBuilder.java +++ b/src/core/lombok/javac/handlers/HandleBuilder.java @@ -347,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) { @@ -810,11 +810,11 @@ 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); } - addCheckReturnValue(methodDef.mods, job.builderType, job.sourceNode); + if (job.checkReturnValue) addCheckReturnValue(methodDef.mods, job.builderType, job.sourceNode); if (staticName == null) createRelevantNonNullAnnotation(job.builderType, methodDef); return methodDef; } - + public static JCMethodDecl generateDefaultProvider(Name methodName, JavacNode fieldNode, List params, BuilderJob job) { JavacTreeMaker maker = fieldNode.getTreeMaker(); JCVariableDecl field = (JCVariableDecl) fieldNode.get(); 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/HandleSuperBuilder.java b/src/core/lombok/javac/handlers/HandleSuperBuilder.java index a48225fa..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; } @@ -873,7 +874,7 @@ public class HandleSuperBuilder extends JavacAnnotationHandler { createRelevantNonNullAnnotation(job.builderType, methodDef); return methodDef; } - + private JCMethodDecl generateCleanMethod(java.util.List builderFields, JavacNode type, JavacNode source) { JavacTreeMaker maker = type.getTreeMaker(); ListBuffer statements = new ListBuffer(); 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 395cae65..bd80b351 100644 --- a/src/core/lombok/javac/handlers/HandleWith.java +++ b/src/core/lombok/javac/handlers/HandleWith.java @@ -281,9 +281,9 @@ public class HandleWith extends JavacAnnotationHandler { List annsOnMethod = copyAnnotations(onMethod, maker); CheckerFrameworkVersion checkerFramework = getCheckerFrameworkVersion(source); if (checkerFramework.generateSideEffectFree()) annsOnMethod = annsOnMethod.prepend(maker.Annotation(genTypeRef(source, CheckerFrameworkVersion.NAME__SIDE_EFFECT_FREE), List.nil())); - + if (isFieldDeprecated(field)) annsOnMethod = annsOnMethod.prepend(maker.Annotation(genJavaLangTypeRef(field, "Deprecated"), List.nil())); - + if (makeAbstract) access |= Flags.ABSTRACT; AnnotationValues accessors = JavacHandlerUtil.getAccessorsForField(field); boolean makeFinal = shouldMakeFinal(field, accessors); diff --git a/src/core/lombok/javac/handlers/JavacHandlerUtil.java b/src/core/lombok/javac/handlers/JavacHandlerUtil.java index dc459d6e..f2ac8e4d 100644 --- a/src/core/lombok/javac/handlers/JavacHandlerUtil.java +++ b/src/core/lombok/javac/handlers/JavacHandlerUtil.java @@ -1556,18 +1556,12 @@ public class JavacHandlerUtil { } public static void addCheckReturnValue(JCModifiers mods, JavacNode node, JavacNode source) { - CheckReturnValueFlavor flavor = node.getAst().readConfiguration(ConfigurationKeys.CHECK_RETURN_VALUE_ANNOTATION); - if (flavor == null) flavor = CheckReturnValueFlavor.NONE; - switch (flavor) { - case LOMBOK: - addAnnotation(mods, node, source, "lombok.CheckReturnValue", null); - return; - case NONE: - default: - return; + 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; {