diff --git a/AUTHORS b/AUTHORS index 380ed790cf..8bc1cd99e0 100755 --- a/AUTHORS +++ b/AUTHORS @@ -65,5 +65,6 @@ Till Brychcy Victor Williams Stafusa da Silva Yonatan Sherwin Yun Zhi Lin +Minttu Stenberg 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. diff --git a/src/core/lombok/ConfigurationKeys.java b/src/core/lombok/ConfigurationKeys.java index 997c550bed..043e912608 100644 --- a/src/core/lombok/ConfigurationKeys.java +++ b/src/core/lombok/ConfigurationKeys.java @@ -601,7 +601,21 @@ private ConfigurationKeys() {} * If set, any usage of {@code @ExtensionMethod} results in a warning / error. */ public static final ConfigurationKey EXTENSION_METHOD_FLAG_USAGE = new ConfigurationKey("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 EXTENSION_METHOD_SUPPRESS_BASE_METHODS = new ConfigurationKey("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} += <TypeName: fully-qualified annotation class name>. + * + * All types whose static methods will be exposed as extension methods. + */ + public static final ConfigurationKey> EXTENSION_METHOD_DEFAULT_EXTENSIONS = new ConfigurationKey>("lombok.extensionMethod.defaultExtensions", "All types whose static methods will be exposed as extension methods.") {}; + // ----- FieldDefaults ----- /** diff --git a/src/core/lombok/eclipse/handlers/HandleExtensionMethod.java b/src/core/lombok/eclipse/handlers/HandleExtensionMethod.java index b84018c66d..a917fa5e59 100644 --- a/src/core/lombok/eclipse/handlers/HandleExtensionMethod.java +++ b/src/core/lombok/eclipse/handlers/HandleExtensionMethod.java @@ -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 { - @Override public void handle(AnnotationValues 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 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 = null; + EclipseNode source = typeNode; + + List 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; } + } } diff --git a/src/core/lombok/javac/handlers/HandleExtensionMethod.java b/src/core/lombok/javac/handlers/HandleExtensionMethod.java index 6e2f6a30d0..e5e3fac49f 100644 --- a/src/core/lombok/javac/handlers/HandleExtensionMethod.java +++ b/src/core/lombok/javac/handlers/HandleExtensionMethod.java @@ -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; @@ -55,6 +55,7 @@ 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; @@ -62,41 +63,68 @@ 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 { - @Override - public void handle(final AnnotationValues 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 = 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 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 defaultExtensions = findDefaultExtensions(typeNode); + + List 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 extensions = getExtensions(annotationNode, extensionProviders); - if (extensions.isEmpty()) return; - - new ExtensionMethodReplaceVisitor(annotationNode, extensions, suppressBaseMethods).replace(); - - annotationNode.rebuild(); + + final List 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 getExtensions(final JavacNode typeNode, final List extensionProviders) { List extensions = new ArrayList(); @@ -126,6 +154,29 @@ public Extension getExtension(final JavacNode typeNode, final ClassType extensio } return new Extension(extensionMethods, tsym); } + + public List findDefaultExtensions(JavacNode typeNode) { + java.util.List configuredDefaults = typeNode.getAst().readConfiguration(ConfigurationKeys.EXTENSION_METHOD_DEFAULT_EXTENSIONS); + if (configuredDefaults.isEmpty()) return Collections.emptyList(); + + List extensions = new ArrayList(); + 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 extensionMethods; diff --git a/src/core/lombok/javac/handlers/JavacHandlerUtil.java b/src/core/lombok/javac/handlers/JavacHandlerUtil.java index 0c075295b3..e8d22aa98b 100644 --- a/src/core/lombok/javac/handlers/JavacHandlerUtil.java +++ b/src/core/lombok/javac/handlers/JavacHandlerUtil.java @@ -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; @@ -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(); } diff --git a/src/stubs/com/sun/tools/javac/code/Symtab.java b/src/stubs/com/sun/tools/javac/code/Symtab.java index 89ed54788d..54024253f1 100644 --- a/src/stubs/com/sun/tools/javac/code/Symtab.java +++ b/src/stubs/com/sun/tools/javac/code/Symtab.java @@ -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 @@ -21,4 +22,5 @@ public class Symtab { // JDK 9 public ModuleSymbol unnamedModule; + public ModuleSymbol inferModule(Name packageName) {return null;} } diff --git a/src/stubs/com/sun/tools/javac/main/JavaCompiler.java b/src/stubs/com/sun/tools/javac/main/JavaCompiler.java index d0e7b38f41..3f0e203437 100644 --- a/src/stubs/com/sun/tools/javac/main/JavaCompiler.java +++ b/src/stubs/com/sun/tools/javac/main/JavaCompiler.java @@ -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 ""; } public JCCompilationUnit parse(String fileName) throws IOException { return null; } public List enterTrees(List roots) {return null;} diff --git a/src/utils/lombok/javac/Javac.java b/src/utils/lombok/javac/Javac.java index 840c32ee82..624d52cdff 100644 --- a/src/utils/lombok/javac/Javac.java +++ b/src/utils/lombok/javac/Javac.java @@ -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; @@ -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]); @@ -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); } @@ -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 {