diff --git a/buildScripts/tests.ant.xml b/buildScripts/tests.ant.xml
index 82292dc0..139ef0da 100644
--- a/buildScripts/tests.ant.xml
+++ b/buildScripts/tests.ant.xml
@@ -167,6 +167,11 @@ This buildfile is part of projectlombok.org. It takes care of compiling and runn
+
+
+
+
+
Running TestJavac on JVM${ant.java.version}, with the javac built into your VM distributon.
diff --git a/src/delombok/lombok/delombok/DocCommentIntegrator.java b/src/delombok/lombok/delombok/DocCommentIntegrator.java
index dc71fcb4..8c2b4f6a 100644
--- a/src/delombok/lombok/delombok/DocCommentIntegrator.java
+++ b/src/delombok/lombok/delombok/DocCommentIntegrator.java
@@ -152,6 +152,13 @@ public class DocCommentIntegrator {
return pos;
}
+ @SuppressWarnings("unused") // As of JDK27 this is the only getEndPosition; EndPosTable is gone.
+ public int getEndPosition() {
+ int end = Javac.getTreeEndPos(node);
+ if (end > pos) return end;
+ return pos + docCommentContent_.length();
+ }
+
@SuppressWarnings("unused") // We compile against very old versions of javac intentionally (to support old stuff), but this is the method for newer impls.
public int getEndPosition(EndPosTable endPosTable) {
int end = endPosTable == null ? 0 : endPosTable.getEndPos(node);
diff --git a/src/delombok/lombok/delombok/PrettyPrinter.java b/src/delombok/lombok/delombok/PrettyPrinter.java
index f379094a..be99c401 100644
--- a/src/delombok/lombok/delombok/PrettyPrinter.java
+++ b/src/delombok/lombok/delombok/PrettyPrinter.java
@@ -86,7 +86,6 @@ import com.sun.tools.javac.tree.JCTree.JCVariableDecl;
import com.sun.tools.javac.tree.JCTree.JCWhileLoop;
import com.sun.tools.javac.tree.JCTree.JCWildcard;
import com.sun.tools.javac.tree.JCTree.TypeBoundKind;
-import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition;
import com.sun.tools.javac.util.List;
import com.sun.tools.javac.util.Name;
import com.sun.tools.javac.util.Position;
@@ -1615,17 +1614,10 @@ public class PrettyPrinter extends JCTree.Visitor {
}
}
- private static final Method getExtendsClause, getEndPosition;
+ private static final Method getExtendsClause;
static {
getExtendsClause = getMethod(JCClassDecl.class, "getExtendsClause", new Class>[0]);
-
- if (getJavaCompilerVersion() < 8) {
- getEndPosition = getMethod(DiagnosticPosition.class, "getEndPosition", java.util.Map.class);
- } else {
- getEndPosition = getMethod(DiagnosticPosition.class, "getEndPosition", "com.sun.tools.javac.tree.EndPosTable");
- }
- Permit.setAccessible(getEndPosition);
}
private static Method getMethod(Class> clazz, String name, Class>... paramTypes) {
@@ -1636,18 +1628,6 @@ public class PrettyPrinter extends JCTree.Visitor {
}
}
- private static Method getMethod(Class> clazz, String name, String... paramTypes) {
- try {
- Class>[] c = new Class[paramTypes.length];
- for (int i = 0; i < paramTypes.length; i++) c[i] = Class.forName(paramTypes[i]);
- return Permit.getMethod(clazz, name, c);
- } catch (NoSuchMethodException e) {
- throw sneakyThrow(e);
- } catch (ClassNotFoundException e) {
- throw sneakyThrow(e);
- }
- }
-
public static JCTree getExtendsClause(JCClassDecl decl) {
return (JCTree) Permit.invokeSneaky(getExtendsClause, decl);
}
diff --git a/src/stubs/com/sun/tools/javac/parser/JavacParser.java b/src/stubs/com/sun/tools/javac/parser/JavacParser.java
index 4f1f3380..f328556e 100644
--- a/src/stubs/com/sun/tools/javac/parser/JavacParser.java
+++ b/src/stubs/com/sun/tools/javac/parser/JavacParser.java
@@ -6,6 +6,7 @@ package com.sun.tools.javac.parser;
import com.sun.tools.javac.tree.JCTree;
public class JavacParser {
+ // Up to javac26 the last boolean is keepEndPositions; as of javac27 (JDK-8372948) it is parseModuleInfo.
protected JavacParser(ParserFactory fac, Lexer S, boolean keepDocComments, boolean keepLineMap, boolean keepEndPositions) {
}
diff --git a/src/utils/lombok/javac/CommentCatcher.java b/src/utils/lombok/javac/CommentCatcher.java
index 90266c26..c6cab588 100644
--- a/src/utils/lombok/javac/CommentCatcher.java
+++ b/src/utils/lombok/javac/CommentCatcher.java
@@ -44,7 +44,8 @@ public class CommentCatcher {
setInCompiler(compiler, context);
compiler.keepComments = true;
- compiler.genEndPos = true;
+ // genEndPos no longer exists as of javac27.
+ if (!Javac.endPosStoredOnTree()) compiler.genEndPos = true;
return new CommentCatcher(compiler);
}
@@ -105,6 +106,9 @@ public class CommentCatcher {
parserFactory = Class.forName("lombok.javac.java7.CommentCollectingParserFactory");
} else if (javaCompilerVersion == 8) {
parserFactory = Class.forName("lombok.javac.java8.CommentCollectingParserFactory");
+ } else if (Javac.endPosStoredOnTree()) {
+ // JDK-8372948 also reshaped JavacParser's constructors.
+ parserFactory = Class.forName("lombok.javac.java27.CommentCollectingParserFactory");
} else {
parserFactory = Class.forName("lombok.javac.java9.CommentCollectingParserFactory");
}
diff --git a/src/utils/lombok/javac/Javac.java b/src/utils/lombok/javac/Javac.java
index 840c32ee..fc4724ce 100644
--- a/src/utils/lombok/javac/Javac.java
+++ b/src/utils/lombok/javac/Javac.java
@@ -213,37 +213,51 @@ public class Javac {
private static final Method getExtendsClause, getEndPosition, storeEnd;
+ /** As of javac27 (JDK-8372948), end positions live in {@code JCTree.endpos} and {@code EndPosTable} no longer exists. */
+ private static final boolean ENDPOS_ON_TREE;
+
static {
getExtendsClause = getMethod(JCClassDecl.class, "getExtendsClause", new Class>[0]);
if (getJavaCompilerVersion() < 8) {
+ ENDPOS_ON_TREE = false;
getEndPosition = getMethod(DiagnosticPosition.class, "getEndPosition", java.util.Map.class);
storeEnd = getMethod(java.util.Map.class, "put", Object.class, Object.class);
} else {
- getEndPosition = getMethod(DiagnosticPosition.class, "getEndPosition", "com.sun.tools.javac.tree.EndPosTable");
- Method storeEndMethodTemp;
Class> endPosTable;
try {
endPosTable = Class.forName("com.sun.tools.javac.tree.EndPosTable");
} catch (ClassNotFoundException ex) {
- throw sneakyThrow(ex);
+ endPosTable = null;
}
- try {
- storeEndMethodTemp = Permit.getMethod(endPosTable, "storeEnd", JCTree.class, int.class);
- } catch (NoSuchMethodException e) {
+ ENDPOS_ON_TREE = endPosTable == null;
+ if (ENDPOS_ON_TREE) {
+ getEndPosition = getMethod(DiagnosticPosition.class, "getEndPosition", new Class>[0]);
+ storeEnd = null;
+ } else {
+ getEndPosition = getMethod(DiagnosticPosition.class, "getEndPosition", endPosTable);
+ Method storeEndMethodTemp;
try {
- endPosTable = Class.forName("com.sun.tools.javac.parser.JavacParser$AbstractEndPosTable");
storeEndMethodTemp = Permit.getMethod(endPosTable, "storeEnd", JCTree.class, int.class);
- } catch (NoSuchMethodException ex) {
- throw sneakyThrow(ex);
- } catch (ClassNotFoundException ex) {
- throw sneakyThrow(ex);
+ } catch (NoSuchMethodException e) {
+ try {
+ endPosTable = Class.forName("com.sun.tools.javac.parser.JavacParser$AbstractEndPosTable");
+ storeEndMethodTemp = Permit.getMethod(endPosTable, "storeEnd", JCTree.class, int.class);
+ } catch (NoSuchMethodException ex) {
+ throw sneakyThrow(ex);
+ } catch (ClassNotFoundException ex) {
+ throw sneakyThrow(ex);
+ }
}
+ storeEnd = storeEndMethodTemp;
}
- storeEnd = storeEndMethodTemp;
}
Permit.setAccessible(getEndPosition);
- Permit.setAccessible(storeEnd);
+ if (storeEnd != null) Permit.setAccessible(storeEnd);
+ }
+
+ static boolean endPosStoredOnTree() {
+ return ENDPOS_ON_TREE;
}
private static Method getMethod(Class> clazz, String name, Class>... paramTypes) {
@@ -423,6 +437,7 @@ public class Javac {
public static int getEndPosition(DiagnosticPosition pos, JCCompilationUnit top) {
try {
+ if (ENDPOS_ON_TREE) return (Integer) getEndPosition.invoke(pos);
Object endPositions = JCCOMPILATIONUNIT_ENDPOSITIONS.get(top);
return (Integer) getEndPosition.invoke(pos, endPositions);
} catch (IllegalAccessException e) {
@@ -432,8 +447,22 @@ public class Javac {
}
}
+ /** Returns -1 if this javac has no {@code JCTree.endpos} field (pre-27). */
+ public static int getTreeEndPos(JCTree tree) {
+ if (JCTREE_ENDPOS == null) return -1;
+ try {
+ return JCTREE_ENDPOS.getInt(tree);
+ } catch (IllegalAccessException e) {
+ throw sneakyThrow(e);
+ }
+ }
+
public static void storeEnd(JCTree tree, int pos, JCCompilationUnit top) {
try {
+ if (ENDPOS_ON_TREE) {
+ if (JCTREE_ENDPOS != null) JCTREE_ENDPOS.setInt(tree, pos);
+ return;
+ }
Object endPositions = JCCOMPILATIONUNIT_ENDPOSITIONS.get(top);
if (endPositions == null) return;
storeEnd.invoke(endPositions, tree, pos);
@@ -513,6 +542,15 @@ public class Javac {
}
}
+ private static final Field JCTREE_ENDPOS;
+ static {
+ Field f = null;
+ try {
+ f = Permit.getField(JCTree.class, "endpos");
+ } catch (NoSuchFieldException e) {}
+ JCTREE_ENDPOS = f;
+ }
+
private static final Field JCCOMPILATIONUNIT_ENDPOSITIONS, JCCOMPILATIONUNIT_DOCCOMMENTS;
static {
Field f = null;
diff --git a/src/utils/lombok/javac/java27/CommentCollectingParser.java b/src/utils/lombok/javac/java27/CommentCollectingParser.java
new file mode 100644
index 00000000..01c37aae
--- /dev/null
+++ b/src/utils/lombok/javac/java27/CommentCollectingParser.java
@@ -0,0 +1,52 @@
+/*
+ * 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.javac.java27;
+
+import static lombok.javac.CommentCatcher.JCCompilationUnit_comments;
+import static lombok.javac.CommentCatcher.JCCompilationUnit_textBlockStarts;
+
+import lombok.javac.java8.CommentCollectingScanner;
+
+import com.sun.tools.javac.parser.JavacParser;
+import com.sun.tools.javac.parser.Lexer;
+import com.sun.tools.javac.parser.ParserFactory;
+import com.sun.tools.javac.tree.JCTree.JCCompilationUnit;
+
+class CommentCollectingParser extends JavacParser {
+ private final Lexer lexer;
+
+ // The stub constructor's last param is named keepEndPositions, but on javac27 this descriptor resolves to the constructor ending in parseModuleInfo.
+ protected CommentCollectingParser(ParserFactory fac, Lexer S,
+ boolean keepDocComments, boolean keepLineMap, boolean parseModuleInfo) {
+ super(fac, S, keepDocComments, keepLineMap, parseModuleInfo);
+ lexer = S;
+ }
+
+ public JCCompilationUnit parseCompilationUnit() {
+ JCCompilationUnit result = super.parseCompilationUnit();
+ if (lexer instanceof CommentCollectingScanner) {
+ JCCompilationUnit_comments.set(result, ((CommentCollectingScanner) lexer).getComments());
+ JCCompilationUnit_textBlockStarts.set(result, ((CommentCollectingScanner) lexer).getTextBlockStarts());
+ }
+ return result;
+ }
+}
diff --git a/src/utils/lombok/javac/java27/CommentCollectingParserFactory.java b/src/utils/lombok/javac/java27/CommentCollectingParserFactory.java
new file mode 100644
index 00000000..7e0c1acf
--- /dev/null
+++ b/src/utils/lombok/javac/java27/CommentCollectingParserFactory.java
@@ -0,0 +1,66 @@
+/*
+ * 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.javac.java27;
+
+import java.lang.reflect.Field;
+
+import com.sun.tools.javac.main.JavaCompiler;
+import com.sun.tools.javac.parser.JavacParser;
+import com.sun.tools.javac.parser.Lexer;
+import com.sun.tools.javac.parser.ParserFactory;
+import com.sun.tools.javac.parser.ScannerFactory;
+import com.sun.tools.javac.util.Context;
+
+import lombok.permit.Permit;
+
+public class CommentCollectingParserFactory extends ParserFactory {
+ private final Context context;
+
+ static Context.Key key() {
+ return parserFactoryKey;
+ }
+
+ protected CommentCollectingParserFactory(Context context) {
+ super(context);
+ this.context = context;
+ }
+
+ public JavacParser newParser(CharSequence input, boolean keepDocComments, boolean keepLineMap, boolean parseModuleInfo) {
+ ScannerFactory scannerFactory = ScannerFactory.instance(context);
+ Lexer lexer = scannerFactory.newScanner(input, true);
+ Object x = new CommentCollectingParser(this, lexer, true, keepLineMap, parseModuleInfo);
+ return (JavacParser) x;
+ // CCP is based on a stub which extends nothing, but at runtime the stub is replaced with
+ //javac's JavacParser. Either way this will work out.
+ }
+
+ public static void setInCompiler(JavaCompiler compiler, Context context) {
+ context.put(CommentCollectingParserFactory.key(), (ParserFactory) null);
+ Field field;
+ try {
+ field = Permit.getField(JavaCompiler.class, "parserFactory");
+ field.set(compiler, new CommentCollectingParserFactory(context));
+ } catch (Exception e) {
+ throw new IllegalStateException("Could not set comment sensitive parser in the compiler", e);
+ }
+ }
+}