Skip to content
Open
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
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -65,5 +65,6 @@ Till Brychcy <till.brychcy@mercateo.com>
Victor Williams Stafusa da Silva <victorwssilva@gmail.com>
Yonatan Sherwin <yonatansherwin@gmail.com>
Yun Zhi Lin <yun@yunspace.com>
Minttu Stenberg <screret@screret.dev>

By adding your name to this list, you grant full and irrevocable copyright and patent indemnity to Project Lombok and all use of Project Lombok in relation to all commits you add to Project Lombok, and you certify that you have the right to do so.
16 changes: 15 additions & 1 deletion src/core/lombok/ConfigurationKeys.java
Original file line number Diff line number Diff line change
Expand Up @@ -601,7 +601,21 @@ private ConfigurationKeys() {}
* If set, <em>any</em> usage of {@code @ExtensionMethod} results in a warning / error.
*/
public static final ConfigurationKey<FlagUsageType> EXTENSION_METHOD_FLAG_USAGE = new ConfigurationKey<FlagUsageType>("lombok.extensionMethod.flagUsage", "Emit a warning or error if @ExtensionMethod is used.") {};


/**
* lombok configuration: {@code lombok.extensionMethod.defaultSuppressBaseMethods} = {@code true} | {@code false}.
*
* For any class without an {@code @ExtensionMethod} that explicitly defines the {@code suppressBaseMethods} option, this value is used (default = true).
*/
public static final ConfigurationKey<Boolean> EXTENSION_METHOD_SUPPRESS_BASE_METHODS = new ConfigurationKey<Boolean>("lombok.extensionMethod.suppressBaseMethods", "If true, an applicable extension method is used (if found) even if the method call already was compilable (this is the default). If false, an extension method is only used if the method call is not also defined by the type itself..") {};

/**
* lombok configuration: {@code lombok.extensionMethod.defaultExtensions} += &lt;TypeName: fully-qualified annotation class name&gt;.
*
* All types whose static methods will be exposed as extension methods.
*/
public static final ConfigurationKey<List<TypeName>> EXTENSION_METHOD_DEFAULT_EXTENSIONS = new ConfigurationKey<List<TypeName>>("lombok.extensionMethod.defaultExtensions", "All types whose static methods will be exposed as extension methods.") {};

// ----- FieldDefaults -----

/**
Expand Down
82 changes: 58 additions & 24 deletions src/core/lombok/eclipse/handlers/HandleExtensionMethod.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,45 +22,79 @@
package lombok.eclipse.handlers;

import static lombok.core.handlers.HandlerUtil.*;
import static lombok.eclipse.handlers.EclipseHandlerUtil.*;

import java.util.Arrays;
import java.util.List;

import lombok.core.AST;
import lombok.eclipse.*;
import org.eclipse.jdt.internal.compiler.ast.Annotation;
import org.eclipse.jdt.internal.compiler.ast.QualifiedTypeReference;
import org.eclipse.jdt.internal.compiler.ast.SingleTypeReference;
import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration;
import org.eclipse.jdt.internal.compiler.ast.TypeReference;
import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;

import lombok.ConfigurationKeys;
import lombok.core.AnnotationValues;
import lombok.core.HandlerPriority;
import lombok.eclipse.EclipseAnnotationHandler;
import lombok.eclipse.EclipseNode;
import lombok.experimental.ExtensionMethod;
import lombok.spi.Provides;

// This handler just does some additional error checking; the real work is done in the agent.
@Provides
@Provides(EclipseASTVisitor.class)
@HandlerPriority(66560) // 2^16 + 2^10; we must run AFTER HandleVal which is at 2^16
public class HandleExtensionMethod extends EclipseAnnotationHandler<ExtensionMethod> {
@Override public void handle(AnnotationValues<ExtensionMethod> annotation, Annotation ast, EclipseNode annotationNode) {
handleExperimentalFlagUsage(annotationNode, ConfigurationKeys.EXTENSION_METHOD_FLAG_USAGE, "@ExtensionMethod");

TypeDeclaration typeDecl = null;
EclipseNode owner = annotationNode.up();
if (owner.get() instanceof TypeDeclaration) typeDecl = (TypeDeclaration) owner.get();
int modifiers = typeDecl == null ? 0 : typeDecl.modifiers;

boolean notAClass = (modifiers &
(ClassFileConstants.AccAnnotation)) != 0;

if (typeDecl == null || notAClass) {
annotationNode.addError("@ExtensionMethod is legal only on classes and enums and interfaces.");
return;
}

List<Object> listenerInterfaces = annotation.getActualExpressions("value");
if (listenerInterfaces.isEmpty()) {
annotationNode.addWarning(String.format("@ExtensionMethod has no effect since no extension types were specified."));
return;
public class HandleExtensionMethod extends EclipseASTAdapter {

private static final char[] EXTENSION_METHOD = "ExtensionMethod".toCharArray();

@Override public void visitType(EclipseNode typeNode, TypeDeclaration typeDecl) {
int modifiers = typeDecl.modifiers;
boolean notAClass = (modifiers & (ClassFileConstants.AccAnnotation)) != 0;

AnnotationValues<ExtensionMethod> extensionMethod = null;
EclipseNode source = typeNode;

List<Object> listenerInterfaces = null;
boolean suppressBaseMethodsIsExplicit = false;
ExtensionMethod em = null;
for (EclipseNode jn : typeNode.down()) {
if (jn.getKind() != AST.Kind.ANNOTATION) continue;
Annotation ann = (Annotation) jn.get();
TypeReference typeTree = ann.type;
if (typeTree == null) continue;
if (typeTree instanceof SingleTypeReference) {
char[] t = ((SingleTypeReference) typeTree).token;
if (!Arrays.equals(t, EXTENSION_METHOD)) continue;
} else if (typeTree instanceof QualifiedTypeReference) {
char[][] t = ((QualifiedTypeReference) typeTree).tokens;
if (!Eclipse.nameEquals(t, "lombok.experimental.ExtensionMethod")) continue;
} else {
continue;
}

if (!typeMatches(ExtensionMethod.class, jn, typeTree)) continue;

source = jn;
extensionMethod = createAnnotation(ExtensionMethod.class, jn);
suppressBaseMethodsIsExplicit = extensionMethod.isExplicit("suppressBaseMethods");

handleExperimentalFlagUsage(jn, ConfigurationKeys.EXTENSION_METHOD_FLAG_USAGE, "@ExtensionMethod");

em = extensionMethod.getInstance();
if (notAClass) {
jn.addError("@ExtensionMethod is legal only on classes and enums and interfaces.");
return;
}

listenerInterfaces = extensionMethod.getActualExpressions("value");
if (listenerInterfaces.isEmpty()) {
jn.addWarning("@ExtensionMethod has no effect since no extension types were specified.");
return;
}
break;
}

}
}
113 changes: 82 additions & 31 deletions src/core/lombok/javac/handlers/HandleExtensionMethod.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,19 +26,19 @@
import static lombok.javac.handlers.JavacHandlerUtil.*;
import static lombok.javac.handlers.JavacResolver.*;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.*;

import javax.lang.model.element.ElementKind;

import lombok.ConfigurationKeys;
import lombok.core.AST;
import lombok.core.AnnotationValues;
import lombok.core.HandlerPriority;
import lombok.core.configuration.TypeName;
import lombok.experimental.ExtensionMethod;
import lombok.javac.JavacAnnotationHandler;
import lombok.javac.Javac;
import lombok.javac.JavacASTAdapter;
import lombok.javac.JavacASTVisitor;
import lombok.javac.JavacNode;
import lombok.javac.JavacResolution;
import lombok.spi.Provides;
Expand All @@ -55,48 +55,76 @@
import com.sun.tools.javac.code.Type.ForAll;
import com.sun.tools.javac.code.Type.MethodType;
import com.sun.tools.javac.code.Types;
import com.sun.tools.javac.main.JavaCompiler;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.JCTree.JCAnnotation;
import com.sun.tools.javac.tree.JCTree.JCClassDecl;
import com.sun.tools.javac.tree.JCTree.JCExpression;
import com.sun.tools.javac.tree.JCTree.JCFieldAccess;
import com.sun.tools.javac.tree.JCTree.JCIdent;
import com.sun.tools.javac.tree.JCTree.JCMethodInvocation;
import com.sun.tools.javac.util.Convert;
import com.sun.tools.javac.util.Name;

/**
* Handles the {@link ExtensionMethod} annotation for javac.
*/
@Provides
@Provides(JavacASTVisitor.class)
@HandlerPriority(66560) // 2^16 + 2^10; we must run AFTER HandleVal which is at 2^16
public class HandleExtensionMethod extends JavacAnnotationHandler<ExtensionMethod> {
@Override
public void handle(final AnnotationValues<ExtensionMethod> annotation, final JCAnnotation source, final JavacNode annotationNode) {
handleExperimentalFlagUsage(annotationNode, ConfigurationKeys.EXTENSION_METHOD_FLAG_USAGE, "@ExtensionMethod");

deleteAnnotationIfNeccessary(annotationNode, ExtensionMethod.class);
JavacNode typeNode = annotationNode.up();
public class HandleExtensionMethod extends JavacASTAdapter {
@Override public void visitType(JavacNode typeNode, JCClassDecl type) {
boolean isClassEnumInterfaceOrRecord = isClassEnumInterfaceOrRecord(typeNode);

if (!isClassEnumInterfaceOrRecord) {
annotationNode.addError("@ExtensionMethod can only be used on a class, an enum, an interface or a record");
return;

AnnotationValues<ExtensionMethod> extensionMethod = null;
JavacNode source = typeNode;

boolean suppressBaseMethodsIsExplicit = false;
ExtensionMethod em = null;
for (JavacNode jn : typeNode.down()) {
if (jn.getKind() != AST.Kind.ANNOTATION) continue;
JCAnnotation ann = (JCAnnotation) jn.get();
JCTree typeTree = ann.annotationType;
if (typeTree == null) continue;
String typeTreeToString = typeTree.toString();
if (!typeTreeToString.equals("ExtensionMethod") && !typeTreeToString.equals("lombok.experimental.ExtensionMethod")) continue;
if (!typeMatches(ExtensionMethod.class, jn, typeTree)) continue;

source = jn;
extensionMethod = createAnnotation(ExtensionMethod.class, jn);
deleteAnnotationIfNeccessary(jn, ExtensionMethod.class);

suppressBaseMethodsIsExplicit = extensionMethod.isExplicit("suppressBaseMethods");

handleExperimentalFlagUsage(jn, ConfigurationKeys.EXTENSION_METHOD_FLAG_USAGE, "@ExtensionMethod");

em = extensionMethod.getInstance();
if (!isClassEnumInterfaceOrRecord) {
jn.addError("@ExtensionMethod can only be used on a class, an enum, an interface or a record");
return;
}
break;
}

boolean suppressBaseMethods = annotation.getInstance().suppressBaseMethods();

List<Object> extensionProviders = annotation.getActualExpressions("value");
if (extensionProviders.isEmpty()) {
annotationNode.addError(String.format("@%s has no effect since no extension types were specified.", ExtensionMethod.class.getName()));

boolean defaultSuppressBaseMethods = suppressBaseMethodsIsExplicit ? true : !Boolean.FALSE.equals(typeNode.getAst().readConfiguration(ConfigurationKeys.EXTENSION_METHOD_SUPPRESS_BASE_METHODS));
List<Extension> defaultExtensions = findDefaultExtensions(typeNode);

List<Object> extensionProviders = extensionMethod != null ? extensionMethod.getActualExpressions("value") : Collections.emptyList();
if (extensionMethod != null && extensionProviders.isEmpty() && !defaultExtensions.isEmpty()) {
source.addWarning("@ExtensionMethod has no effect since no extension types were specified.");
return;
}
final List<Extension> extensions = getExtensions(annotationNode, extensionProviders);
if (extensions.isEmpty()) return;

new ExtensionMethodReplaceVisitor(annotationNode, extensions, suppressBaseMethods).replace();

annotationNode.rebuild();

final List<Extension> extensions = getExtensions(source, extensionProviders);
if (extensions.isEmpty() && defaultExtensions.isEmpty()) return;
extensions.addAll(defaultExtensions);

boolean emSuppressBaseMethods = (extensionMethod != null && suppressBaseMethodsIsExplicit) ? em.suppressBaseMethods() : defaultSuppressBaseMethods;

new ExtensionMethodReplaceVisitor(source, extensions, emSuppressBaseMethods).replace();

source.rebuild();
}


public List<Extension> getExtensions(final JavacNode typeNode, final List<Object> extensionProviders) {
List<Extension> extensions = new ArrayList<Extension>();
Expand Down Expand Up @@ -126,6 +154,29 @@ public Extension getExtension(final JavacNode typeNode, final ClassType extensio
}
return new Extension(extensionMethods, tsym);
}

public List<Extension> findDefaultExtensions(JavacNode typeNode) {
java.util.List<TypeName> configuredDefaults = typeNode.getAst().readConfiguration(ConfigurationKeys.EXTENSION_METHOD_DEFAULT_EXTENSIONS);
if (configuredDefaults.isEmpty()) return Collections.<Extension>emptyList();

List<Extension> extensions = new ArrayList<Extension>();
for (TypeName cn : configuredDefaults) {
Name name = typeNode.toName(cn.getName());

Object module = null;
if (Javac.getJavaCompilerVersion() >= 9) {
module = typeNode.getSymbolTable().inferModule(Convert.packagePart(name));
if (module == null) {
module = typeNode.getSymbolTable().unnamedModule;
}
}
ClassSymbol classSymbol = Javac.resolveIdent(JavaCompiler.instance(typeNode.getContext()), module, cn.getName());
if ((classSymbol.flags() & (INTERFACE | ANNOTATION)) != 0) continue;

extensions.add(getExtension(typeNode, (ClassType) classSymbol.type));
}
return extensions;
}

private static class Extension {
final List<MethodSymbol> extensionMethods;
Expand Down
4 changes: 2 additions & 2 deletions src/core/lombok/javac/handlers/JavacHandlerUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,7 @@ public static boolean typeMatches(String type, JavacNode node, JCTree typeNode)
return typeMatches(type, node, typeName);
}

private static boolean typeMatches(String type, JavacNode node, String typeName) {
static boolean typeMatches(String type, JavacNode node, String typeName) {
if (typeName == null || typeName.length() == 0) return false;
int lastIndexA = typeName.lastIndexOf('.') + 1;
int lastIndexB = Math.max(type.lastIndexOf('.'), type.lastIndexOf('$')) + 1;
Expand All @@ -333,7 +333,7 @@ private static boolean typeMatches(String type, JavacNode node, String typeName)
return resolver.typeMatches(node, type, typeName);
}

private static String getTypeName(JCTree typeNode) {
static String getTypeName(JCTree typeNode) {
return typeNode == null ? null : typeNode.toString();
}

Expand Down
2 changes: 2 additions & 0 deletions src/stubs/com/sun/tools/javac/code/Symtab.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import com.sun.tools.javac.code.Symbol.ModuleSymbol;
import com.sun.tools.javac.code.Symbol.TypeSymbol;
import com.sun.tools.javac.util.Context;
import com.sun.tools.javac.util.Name;

public class Symtab {
// Shared by JDK6-9
Expand All @@ -21,4 +22,5 @@ public class Symtab {

// JDK 9
public ModuleSymbol unnamedModule;
public ModuleSymbol inferModule(Name packageName) {return null;}
}
1 change: 1 addition & 0 deletions src/stubs/com/sun/tools/javac/main/JavaCompiler.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public class JavaCompiler {

public JavaCompiler(Context context) {}
public int errorCount() { return 0; }
public static JavaCompiler instance(Context context) {return null;}
public static String version() { return "<stub>"; }
public JCCompilationUnit parse(String fileName) throws IOException { return null; }
public List<JCCompilationUnit> enterTrees(List<JCCompilationUnit> roots) {return null;}
Expand Down
24 changes: 22 additions & 2 deletions src/utils/lombok/javac/Javac.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@

import com.sun.tools.javac.code.Flags;
import com.sun.tools.javac.code.Source;
import com.sun.tools.javac.code.Symbol;
import com.sun.tools.javac.code.Symtab;
import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.main.JavaCompiler;
Expand Down Expand Up @@ -211,7 +212,7 @@ public static Object calculateGuess(JCExpression expr) {
public static final TreeTag CTC_POSTINC = treeTag("POSTINC");
public static final TreeTag CTC_POSTDEC = treeTag("POSTDEC");

private static final Method getExtendsClause, getEndPosition, storeEnd;
private static final Method getExtendsClause, getEndPosition, storeEnd, resolveIdent;

static {
getExtendsClause = getMethod(JCClassDecl.class, "getExtendsClause", new Class<?>[0]);
Expand Down Expand Up @@ -242,7 +243,12 @@ public static Object calculateGuess(JCExpression expr) {
}
storeEnd = storeEndMethodTemp;
}
Permit.setAccessible(getEndPosition);
if (getJavaCompilerVersion() >= 9) {
resolveIdent = getMethod(JavaCompiler.class, "resolveIdent", Symbol.ModuleSymbol.class, String.class);
} else {
resolveIdent = getMethod(JavaCompiler.class, "resolveIdent", String.class);
}
Permit.setAccessible(getEndPosition);
Permit.setAccessible(storeEnd);
}

Expand Down Expand Up @@ -444,6 +450,20 @@ public static void storeEnd(JCTree tree, int pos, JCCompilationUnit top) {
}
}

public static Symbol.ClassSymbol resolveIdent(JavaCompiler javaCompiler, Object module, String name) {
try {
if (getJavaCompilerVersion() >= 9) {
return (Symbol.ClassSymbol) resolveIdent.invoke(javaCompiler, module, name);
} else {
return (Symbol.ClassSymbol) resolveIdent.invoke(javaCompiler, name);
}
} catch (IllegalAccessException e) {
throw sneakyThrow(e);
} catch (InvocationTargetException e) {
throw sneakyThrow(e.getCause());
}
}

private static final Class<?> JC_VOID_TYPE, JC_NO_TYPE;

static {
Expand Down