Skip to content

Commit c8f8d92

Browse files
committed
Avoid InvalidSignatureException for intersection types
Fixes #1839
1 parent 00d5ed9 commit c8f8d92

File tree

2 files changed

+124
-5
lines changed

2 files changed

+124
-5
lines changed

org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/javac/CachingClassSymbolClassReader.java

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
import com.sun.tools.javac.code.Symtab;
5454
import com.sun.tools.javac.code.Type;
5555
import com.sun.tools.javac.code.Type.ClassType;
56+
import com.sun.tools.javac.code.Type.IntersectionClassType;
5657
import com.sun.tools.javac.code.Type.MethodType;
5758
import com.sun.tools.javac.code.Type.TypeVar;
5859
import com.sun.tools.javac.code.TypeTag;
@@ -135,7 +136,7 @@ public static void preRegister(Context context) {
135136
context.put(classReaderKey, (Context.Factory<ClassReader>)c -> new CachingClassSymbolClassReader(c));
136137
}
137138

138-
private final Types localTypes;
139+
public final Types localTypes;
139140
private final Names localNames;
140141
private final Symtab localSyms;
141142
private final StoringQueriesAnnotate localAnnotate;
@@ -220,15 +221,26 @@ private static class TypeVariableTemplate {
220221
private final long flags;
221222
private final Name name;
222223
private final String lowerBound;
223-
private final String upperBound;
224+
private final List<String> upperBounds;
224225
private final SymbolMetadataTemplate metadata;
225226
private final List<CompoundTemplate> toAnnotate;
226227

227228
public TypeVariableTemplate(TypeVariableSymbol base, CachingClassSymbolClassReader reader) {
228229
this.flags = base.flags_field;
229230
this.name = base.name;
230231
this.lowerBound = base.type instanceof TypeVar typeVar && typeVar.getLowerBound() != null && typeVar.getLowerBound() != reader.localSyms.botType ? reader.typeToSig(typeVar.getLowerBound()) : null;
231-
this.upperBound = base.type instanceof TypeVar typeVar && typeVar.getUpperBound() != null && typeVar.getUpperBound() != reader.localSyms.botType ? reader.typeToSig(typeVar.getUpperBound()) : null;
232+
List<String> upperBounds = List.of();
233+
if (base.type instanceof TypeVar typeVar && typeVar.getUpperBound() != null && typeVar.getUpperBound() != reader.localSyms.botType ) {
234+
if (typeVar.getUpperBound() instanceof IntersectionClassType intersection) {
235+
upperBounds = intersection.getBounds().stream()
236+
.map(Type.class::cast)
237+
.map(reader::typeToSig)
238+
.toList();
239+
} else {
240+
upperBounds = List.of(reader.typeToSig(typeVar.getUpperBound()));
241+
}
242+
}
243+
this.upperBounds = upperBounds;
232244
this.metadata = new SymbolMetadataTemplate(base.getMetadata(), reader);
233245
this.toAnnotate = reader.localAnnotate.annotationsFor(base);
234246
}
@@ -237,8 +249,14 @@ public TypeVariableSymbol create(Symbol owner, CachingClassSymbolClassReader rea
237249
tvar.tsym.flags_field = this.flags;
238250
// this line needs to be before resolving upper bound as upper bound can reference this type
239251
reader.typevars.enter(tvar.tsym);
240-
if (this.upperBound != null) {
241-
tvar.setUpperBound(reader.sigToType(this.upperBound));
252+
Type upperBound = null;
253+
if (this.upperBounds.size() == 1) {
254+
upperBound = reader.sigToType(this.upperBounds.getFirst());
255+
} else if (this.upperBounds.size() > 1) {
256+
upperBound = reader.localTypes.makeIntersectionType(com.sun.tools.javac.util.List.from(this.upperBounds.stream().map(reader::sigToType).toList()));
257+
}
258+
if (upperBound != null) {
259+
tvar.setUpperBound(upperBound);
242260
}
243261
this.metadata.applyTo(tvar.tsym, reader);
244262
reader.localAnnotate.normal(tvar.tsym, this.toAnnotate);
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2025, Red Hat, Inc. and others.
3+
*
4+
* This program and the accompanying materials
5+
* are made available under the terms of the Eclipse Public License 2.0
6+
* which accompanies this distribution, and is available at
7+
* https://www.eclipse.org/legal/epl-2.0/
8+
*
9+
* SPDX-License-Identifier: EPL-2.0
10+
*******************************************************************************/
11+
package org.eclipse.jdt.core.tests.javac;
12+
13+
import static org.junit.Assert.assertArrayEquals;
14+
import static org.junit.Assert.assertEquals;
15+
import static org.junit.Assert.assertTrue;
16+
17+
import java.io.CharArrayWriter;
18+
import java.nio.file.Files;
19+
import java.nio.file.Path;
20+
import java.util.ArrayList;
21+
import java.util.List;
22+
23+
import javax.tools.JavaCompiler;
24+
import javax.tools.JavaFileObject;
25+
import javax.tools.StandardJavaFileManager;
26+
import javax.tools.ToolProvider;
27+
28+
import org.eclipse.core.runtime.ILog;
29+
import org.eclipse.core.runtime.ILogListener;
30+
import org.eclipse.core.runtime.IStatus;
31+
import org.eclipse.core.runtime.NullProgressMonitor;
32+
import org.eclipse.core.runtime.Platform;
33+
import org.eclipse.jdt.core.compiler.IProblem;
34+
import org.eclipse.jdt.core.dom.AST;
35+
import org.eclipse.jdt.core.dom.ASTParser;
36+
import org.eclipse.jdt.core.dom.CompilationUnit;
37+
import org.eclipse.jdt.core.dom.JavacCompilationUnitResolver;
38+
import org.junit.Test;
39+
40+
public class CachingClassReaderTests {
41+
42+
@Test
43+
public void testIntersectionType() throws Exception {
44+
Path dir = Files.createTempDirectory(getClass().getName());
45+
Path sourceFile = dir.resolve("A.java");
46+
Files.write(sourceFile, """
47+
class A<T extends java.util.function.IntSupplier & java.util.function.LongSupplier> {
48+
T f;
49+
A(T f) {
50+
this.f = f;
51+
}
52+
}
53+
""".getBytes());
54+
55+
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
56+
StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
57+
Iterable<? extends JavaFileObject> compilationUnits1 = fileManager.getJavaFileObjectsFromPaths(List.of(sourceFile));
58+
CharArrayWriter writer = new CharArrayWriter(2000);
59+
assertTrue(compiler.getTask(writer, fileManager, null, List.of("--release", "17"), null, compilationUnits1).call());
60+
Path Bclass = dir.resolve("A.class");
61+
assertTrue(Files.exists(Bclass));
62+
63+
String source = """
64+
class I {
65+
A<Sup> a = new A<>(new Sup());
66+
}
67+
class Sup implements java.util.function.IntSupplier, java.util.function.LongSupplier {
68+
public int getAsInt() { return 0; }
69+
public long getAsLong() { return 0; }
70+
}
71+
""";
72+
// What we really want to test is calling CachingClassSymbolClassReader multiple times for A$B
73+
// but as we prefer avoiding references to internal Javac types in tests, we just load the AST multiple times.
74+
for (int i = 0; i < 2; i++) {
75+
ASTParser parser = ASTParser.newParser(AST.getJLSLatest());
76+
parser.setSource(source.toCharArray());
77+
parser.setUnitName("I.java");
78+
parser.setEnvironment(new String[] { dir.toString() }, null, null, true);
79+
parser.setResolveBindings(true);
80+
try (var _ = withoutLoggedError()) {
81+
var node = (CompilationUnit)parser.createAST(new NullProgressMonitor());
82+
assertArrayEquals(new IProblem[0], node.getProblems());
83+
}
84+
}
85+
}
86+
87+
private AutoCloseable withoutLoggedError() {
88+
ILog log = Platform.getLog(JavacCompilationUnitResolver.class);
89+
List<IStatus> errors = new ArrayList<>();
90+
ILogListener listener = (status, bundle) -> {
91+
if (status.getSeverity() == IStatus.ERROR) {
92+
errors.add(status);
93+
}
94+
};
95+
log.addLogListener(listener);
96+
return () -> {
97+
log.removeLogListener(listener);
98+
assertEquals(List.of(), errors);
99+
};
100+
}
101+
}

0 commit comments

Comments
 (0)