Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/core/lombok/ConfigurationKeys.java
Original file line number Diff line number Diff line change
Expand Up @@ -728,7 +728,9 @@ private ConfigurationKeys() {}
* Copy these annotations to getters, setters, with methods, builder-setters, etc.
*/
public static final ConfigurationKey<List<TypeName>> COPYABLE_ANNOTATIONS = new ConfigurationKey<List<TypeName>>("lombok.copyableAnnotations", "Copy these annotations to getters, setters, with methods, builder-setters, etc.") {};


public static final ConfigurationKey<List<TypeName>> COPYABLE_ANNOTATIONS_FOR_DELEGATE = new ConfigurationKey<List<TypeName>>("lombok.copyableAnnotationsForDelegate", "Copy these annotations to methods generated by @Delegate") {};

/**
* lombok configuration: {@code checkerframework} = {@code true} | {@code false} | &lt;String: MajorVer.MinorVer&gt; (Default: false).
*
Expand Down
77 changes: 75 additions & 2 deletions src/core/lombok/core/handlers/HandlerUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,14 @@ public static int primeForFalse() {
public static int primeForNull() {
return 43;
}

public static final List<String> 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<String> 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,
COPYABLE_ANNOTATIONS_FOR_DELEGATE;
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;
Expand Down Expand Up @@ -489,6 +495,73 @@ public static int primeForNull() {
"com.fasterxml.jackson.annotation.JsonView",
"com.fasterxml.jackson.databind.annotation.JsonNaming",
}));
COPYABLE_ANNOTATIONS_FOR_DELEGATE = Collections.unmodifiableList(Arrays.asList(new String[] {
"android.annotation.NonNull",
"android.annotation.Nullable",
"android.support.annotation.NonNull",
"android.support.annotation.Nullable",
"android.support.annotation.RecentlyNonNull",
"android.support.annotation.RecentlyNullable",
"androidx.annotation.NonNull",
"androidx.annotation.Nullable",
"androidx.annotation.RecentlyNonNull",
"androidx.annotation.RecentlyNullable",
"com.android.annotations.NonNull",
"com.android.annotations.Nullable",
"com.google.firebase.database.annotations.NotNull",
"com.google.firebase.database.annotations.Nullable",
"com.mongodb.lang.NonNull",
"com.mongodb.lang.Nullable",
"com.sun.istack.NotNull",
"com.sun.istack.Nullable",
"com.unboundid.util.NotNull",
"com.unboundid.util.Nullable",
"edu.umd.cs.findbugs.annotations.CheckForNull",
"edu.umd.cs.findbugs.annotations.NonNull",
"edu.umd.cs.findbugs.annotations.Nullable",
"edu.umd.cs.findbugs.annotations.PossiblyNull",
"edu.umd.cs.findbugs.annotations.UnknownNullness",
"io.micrometer.core.lang.NonNull",
"io.micrometer.core.lang.Nullable",
"io.reactivex.annotations.NonNull",
"io.reactivex.annotations.Nullable",
"io.reactivex.rxjava3.annotations.NonNull",
"io.reactivex.rxjava3.annotations.Nullable",
"jakarta.annotation.Nonnull",
"jakarta.annotation.Nullable",
"javax.annotation.CheckForNull",
"javax.annotation.Nonnull",
"javax.annotation.Nullable",
"libcore.util.NonNull",
"libcore.util.Nullable",
"lombok.NonNull",
"org.checkerframework.checker.nullness.compatqual.NonNullDecl",
"org.checkerframework.checker.nullness.compatqual.NonNullType",
"org.checkerframework.checker.nullness.compatqual.NullableDecl",
"org.checkerframework.checker.nullness.compatqual.NullableType",
"org.checkerframework.checker.nullness.qual.NonNull",
"org.checkerframework.checker.nullness.qual.Nullable",
"org.codehaus.commons.nullanalysis.NotNull",
"org.codehaus.commons.nullanalysis.Nullable",
"org.eclipse.jdt.annotation.NonNull",
"org.eclipse.jdt.annotation.Nullable",
"org.jetbrains.annotations.NotNull",
"org.jetbrains.annotations.Nullable",
"org.jetbrains.annotations.UnknownNullability",
"org.jmlspecs.annotation.NonNull",
"org.jmlspecs.annotation.Nullable",
"org.jspecify.annotations.NonNull",
"org.jspecify.annotations.NonNull",
"org.jspecify.annotations.Nullable",
"org.netbeans.api.annotations.common.CheckForNull",
"org.netbeans.api.annotations.common.NonNull",
"org.netbeans.api.annotations.common.NullAllowed",
"org.netbeans.api.annotations.common.NullUnknown",
"org.springframework.lang.NonNull",
"org.springframework.lang.Nullable",
"reactor.util.annotation.NonNull",
"reactor.util.annotation.Nullable",
}));
}

/** Checks if the given name is a valid identifier.
Expand Down
90 changes: 61 additions & 29 deletions src/core/lombok/javac/handlers/HandleDelegate.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,7 @@
import java.util.List;
import java.util.Set;

import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeParameterElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.element.*;
import javax.lang.model.type.ExecutableType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
Expand Down Expand Up @@ -71,6 +67,7 @@
import lombok.core.AST.Kind;
import lombok.core.AnnotationValues;
import lombok.core.HandlerPriority;
import lombok.core.handlers.HandlerUtil;
import lombok.experimental.Delegate;
import lombok.javac.FindTypeVarScanner;
import lombok.javac.JavacAnnotationHandler;
Expand Down Expand Up @@ -295,16 +292,20 @@ public JCMethodDecl createDelegateMethod(MethodSig sig, JavacNode annotation, Na

JavacTreeMaker maker = annotation.getTreeMaker();

com.sun.tools.javac.util.List<JCAnnotation> annotations;
ArrayList<JCAnnotation> methodAnnotationsToCopy = new ArrayList<JCAnnotation>();
if (sig.isDeprecated) {
annotations = com.sun.tools.javac.util.List.of(maker.Annotation(
methodAnnotationsToCopy.add(maker.Annotation(
genJavaLangTypeRef(annotation, "Deprecated"),
com.sun.tools.javac.util.List.<JCExpression>nil()));
} else {
annotations = com.sun.tools.javac.util.List.nil();
}

JCModifiers mods = maker.Modifiers(PUBLIC, annotations);
Set<String> copyableAnnotations = JavacHandlerUtil.getCopyableAnnotationsForDelegate(annotation);
for (Compound sigAnnotation : sig.annotations) {
if (copyableAnnotations.contains(sigAnnotation.type.tsym.flatName().toString())) {
methodAnnotationsToCopy.add(maker.Annotation(sigAnnotation));
}
}

JCModifiers mods = maker.Modifiers(PUBLIC, com.sun.tools.javac.util.List.from(methodAnnotationsToCopy.toArray(new JCAnnotation[0])));
JCExpression returnType = JavacResolution.typeToJCTree((Type) sig.type.getReturnType(), annotation.getAst(), true);
boolean useReturn = sig.type.getReturnType().getKind() != TypeKind.VOID;
ListBuffer<JCVariableDecl> params = sig.type.getParameterTypes().isEmpty() ? null : new ListBuffer<JCVariableDecl>();
Expand All @@ -329,18 +330,26 @@ public JCMethodDecl createDelegateMethod(MethodSig sig, JavacNode annotation, Na
for (TypeMirror ex : sig.type.getThrownTypes()) {
thrown.append(JavacResolution.typeToJCTree((Type) ex, annotation.getAst(), true));
}

int idx = 0;
String[] paramNames = sig.getParameterNames();

boolean varargs = sig.elem.isVarArgs();
for (TypeMirror param : sig.type.getParameterTypes()) {
ParameterSig[] paramSigs = sig.getParameters();
for (int idx = 0; idx < paramSigs.length; idx++) {
ParameterSig paramSig = paramSigs[idx];
long flags = JavacHandlerUtil.addFinalIfNeeded(Flags.PARAMETER, annotation.getContext());
JCModifiers paramMods = maker.Modifiers(flags);
Name name = annotation.toName(paramNames[idx++]);
if (varargs && idx == paramNames.length) {

List<JCAnnotation> paramAnnotationsToCopy = new ArrayList<JCAnnotation>();
for (AnnotationMirror b : paramSig.annotations) {
String fqn = ((TypeElement) b.getAnnotationType().asElement()).getQualifiedName().toString();
if (copyableAnnotations.contains(fqn)) {
paramAnnotationsToCopy.add(maker.Annotation((Compound) b));
}
}
JCModifiers paramMods = maker.Modifiers(flags, com.sun.tools.javac.util.List.from(paramAnnotationsToCopy.toArray(new JCAnnotation[0])));
Name name = annotation.toName(paramSig.name);
if (varargs && idx == paramSigs.length - 1) {
paramMods.flags |= VARARGS;
}
params.append(maker.VarDef(paramMods, name, JavacResolution.typeToJCTree((Type) param, annotation.getAst(), true), null));
params.append(maker.VarDef(paramMods, name, JavacResolution.typeToJCTree((Type) paramSig.type, annotation.getAst(), true), null));
args.append(maker.Ident(name));
}

Expand Down Expand Up @@ -369,6 +378,7 @@ public void addMethodBindings(List<MethodSig> signatures, ClassType ct, JavacTyp
if (tsym == null) return;

for (Symbol member : tsym.getEnclosedElements()) {
ArrayList<Compound> annotations = new ArrayList<Compound>();
for (Compound am : member.getAnnotationMirrors()) {
String name = null;
try {
Expand All @@ -378,6 +388,9 @@ public void addMethodBindings(List<MethodSig> signatures, ClassType ct, JavacTyp
if ("lombok.Delegate".equals(name) || "lombok.experimental.Delegate".equals(name)) {
throw new DelegateRecursion(ct.tsym.name.toString(), member.name.toString());
}

annotations.add(am);

}
if (member.getKind() != ElementKind.METHOD) continue;
if (member.isStatic()) continue;
Expand All @@ -388,7 +401,7 @@ public void addMethodBindings(List<MethodSig> signatures, ClassType ct, JavacTyp
String sig = printSig(methodType, member.name, types);
if (!banList.add(sig)) continue; //If add returns false, it was already in there
boolean isDeprecated = (member.flags() & DEPRECATED) != 0;
signatures.add(new MethodSig(member.name, methodType, isDeprecated, exElem));
signatures.add(new MethodSig(member.name, methodType, isDeprecated, exElem, annotations));
}

for (Type type : types.directSupertypes(ct)) {
Expand All @@ -403,28 +416,47 @@ public static class MethodSig {
final ExecutableType type;
final boolean isDeprecated;
final ExecutableElement elem;

MethodSig(Name name, ExecutableType type, boolean isDeprecated, ExecutableElement elem) {
final List<Compound> annotations;

MethodSig(Name name, ExecutableType type, boolean isDeprecated, ExecutableElement elem, List<Compound> annotations) {
this.name = name;
this.type = type;
this.isDeprecated = isDeprecated;
this.elem = elem;
this.annotations = annotations;
}

String[] getParameterNames() {
List<? extends VariableElement> paramList = elem.getParameters();
String[] paramNames = new String[paramList.size()];
for (int i = 0; i < paramNames.length; i++) {
paramNames[i] = paramList.get(i).getSimpleName().toString();
ParameterSig[] getParameters() {
VariableElement[] params = elem.getParameters().toArray(new VariableElement[0]);
TypeMirror[] paramTypes = type.getParameterTypes().toArray(new TypeMirror[0]);
ParameterSig[] parameterSigs = new ParameterSig[params.length];
for (int i = 0; i < parameterSigs.length; i++) {
parameterSigs[i] = new ParameterSig(
params[i].getSimpleName().toString(),
paramTypes[i],
params[i].getAnnotationMirrors()
);
}
return paramNames;
return parameterSigs;
}

@Override public String toString() {
return (isDeprecated ? "@Deprecated " : "") + name + " " + type;
}
}


public static class ParameterSig {
final String name;
final TypeMirror type;
final List<? extends AnnotationMirror> annotations;

ParameterSig(String name, TypeMirror type, List<? extends AnnotationMirror> annotations) {
this.name = name;
this.type = type;
this.annotations = annotations;
}
}

public static String printSig(ExecutableType method, Name name, JavacTypes types) {
StringBuilder sb = new StringBuilder();
sb.append(name.toString()).append("(");
Expand Down
18 changes: 13 additions & 5 deletions src/core/lombok/javac/handlers/JavacHandlerUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,7 @@
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.*;
import java.util.regex.Pattern;

import com.sun.source.tree.TreeVisitor;
Expand Down Expand Up @@ -1755,7 +1752,18 @@ public static List<JCAnnotation> findCopyableAnnotations(JavacNode node) {

return copyAnnotations(result.toList(), node.getTreeMaker());
}


public static Set<String> getCopyableAnnotationsForDelegate(JavacNode node) {
HashSet<String> copyable = new HashSet<String>();
java.util.List<TypeName> configuredCopyable = node.getAst().readConfiguration(ConfigurationKeys.COPYABLE_ANNOTATIONS_FOR_DELEGATE);
if (configuredCopyable != null) {
for (TypeName cn : configuredCopyable) if (cn != null) copyable.add(cn.toString());
}
copyable.addAll(COPYABLE_ANNOTATIONS_FOR_DELEGATE);

return copyable;
}

/**
* Searches the given field node for annotations that are specifically intended to be copied to the getter.
*
Expand Down
19 changes: 19 additions & 0 deletions test/transform/resource/after-delombok/RecordWithNullable.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//platform !eclipse: Requires a 'full' eclipse with intialized workspace, and we don't (yet) have that set up properly in the test run.
//version 14:
import javax.annotation.Nullable;
import javax.annotation.Tainted;

record DelegateOnRecord(SomeInterface runnable) {
interface SomeInterface {
@Nullable
@Tainted
String getString(@Nullable @Tainted String input);
}

@javax.annotation.Nullable
@java.lang.SuppressWarnings("all")
@lombok.Generated
public java.lang.String getString(@javax.annotation.Nullable final java.lang.String input) {
return this.runnable.getString(input);
}
}
13 changes: 13 additions & 0 deletions test/transform/resource/before/RecordWithNullable.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//platform !eclipse: Requires a 'full' eclipse with intialized workspace, and we don't (yet) have that set up properly in the test run.
//version 14:
import lombok.experimental.Delegate;
import javax.annotation.Nullable;
import javax.annotation.Tainted;

record DelegateOnRecord(@Delegate SomeInterface runnable) {
interface SomeInterface {
@Nullable
@Tainted
String getString(@Nullable @Tainted String input);
}
}