Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.function.IntFunction;
import java.util.function.Predicate;
import java.util.stream.IntStream;

import javax.lang.model.element.Modifier;
import javax.lang.model.element.NestingKind;
Expand All @@ -61,6 +61,7 @@
import com.sun.tools.javac.jvm.ClassFile.Version;
import com.sun.tools.javac.jvm.PoolConstant.NameAndType;
import com.sun.tools.javac.main.Option;
import com.sun.tools.javac.resources.CompilerProperties.Errors;
import com.sun.tools.javac.resources.CompilerProperties.Fragments;
import com.sun.tools.javac.resources.CompilerProperties.Warnings;
import com.sun.tools.javac.util.*;
Expand Down Expand Up @@ -2202,9 +2203,17 @@ public void run() {
* 4.7.20-A target_type to locate the correct type to rewrite, and then interpreting the JVMS
* 4.7.20.2 type_path to associate the annotation with the correct contained type.
*/
private static void addTypeAnnotationsToSymbol(
Symbol s, List<Attribute.TypeCompound> attributes) {
new TypeAnnotationSymbolVisitor(attributes).visit(s, null);
private void addTypeAnnotationsToSymbol(Symbol s, List<Attribute.TypeCompound> attributes) {
try {
new TypeAnnotationSymbolVisitor(attributes).visit(s, null);
} catch (CompletionFailure ex) {
JavaFileObject prev = log.useSource(currentClassFile);
try {
log.error(Errors.CantAttachTypeAnnotations(attributes, s.owner, s.name, ex.getDetailValue()));
} finally {
log.useSource(prev);
}
}
}

private static class TypeAnnotationSymbolVisitor
Expand Down Expand Up @@ -2348,13 +2357,8 @@ private Type addTypeAnnotations(Type type, Predicate<TypeAnnotationPosition> fil
.add(attribute);
}

// Search the structure of the type to find the contained types at each type path
Map<Type, List<Attribute.TypeCompound>> attributesByType = new HashMap<>();
new TypeAnnotationLocator(attributesByPath, attributesByType).visit(type, List.nil());

// Rewrite the type and add the annotations
type = new TypeAnnotationTypeMapping(attributesByType).visit(type, null);
Assert.check(attributesByType.isEmpty(), "Failed to apply annotations to types");
type = new TypeAnnotationStructuralTypeMapping(attributesByPath).visit(type, List.nil());

return type;
}
Expand Down Expand Up @@ -2382,124 +2386,106 @@ private static Predicate<TypeAnnotationPosition> classExtends(int index) {
}

/**
* Visit all contained types, assembling a type path to represent the current location, and
* record the types at each type path that need to be annotated.
* A type mapping that rewrites the type to include type annotations.
*
* <p>This logic is similar to {@link Type.StructuralTypeMapping}, but also tracks the path to
* the contained types being rewritten, and so cannot easily share the existing logic.
*/
private static class TypeAnnotationLocator
extends Types.DefaultTypeVisitor<Void, List<TypeAnnotationPosition.TypePathEntry>> {
private static final class TypeAnnotationStructuralTypeMapping
extends Types.TypeMapping<List<TypeAnnotationPosition.TypePathEntry>> {

private final Map<List<TypeAnnotationPosition.TypePathEntry>,
ListBuffer<Attribute.TypeCompound>> attributesByPath;
private final Map<Type, List<Attribute.TypeCompound>> attributesByType;
ListBuffer<Attribute.TypeCompound>> attributesByPath;

private TypeAnnotationLocator(
private TypeAnnotationStructuralTypeMapping(
Map<List<TypeAnnotationPosition.TypePathEntry>, ListBuffer<Attribute.TypeCompound>>
attributesByPath,
Map<Type, List<Attribute.TypeCompound>> attributesByType) {
attributesByPath) {
this.attributesByPath = attributesByPath;
this.attributesByType = attributesByType;
}


@Override
public Void visitClassType(ClassType t, List<TypeAnnotationPosition.TypePathEntry> path) {
public Type visitClassType(ClassType t, List<TypeAnnotationPosition.TypePathEntry> path) {
// As described in JVMS 4.7.20.2, type annotations on nested types are located with
// 'left-to-right' steps starting on 'the outermost part of the type for which a type
// annotation is admissible'. So the current path represents the outermost containing
// type of the type being visited, and we add type path steps for every contained nested
// type.
List<ClassType> enclosing = List.nil();
for (Type curr = t;
curr != null && curr != Type.noType;
Type outer = t.getEnclosingType();
Type outer1 = outer != Type.noType ? visit(outer, path) : outer;
for (Type curr = t.getEnclosingType();
curr != Type.noType;
curr = curr.getEnclosingType()) {
enclosing = enclosing.prepend((ClassType) curr);
}
for (ClassType te : enclosing) {
if (te.typarams_field != null) {
int i = 0;
for (Type typaram : te.typarams_field) {
visit(typaram, path.append(new TypeAnnotationPosition.TypePathEntry(
TypeAnnotationPosition.TypePathEntryKind.TYPE_ARGUMENT, i++)));
}
}
visitType(te, path);
path = path.append(TypeAnnotationPosition.TypePathEntry.INNER_TYPE);
}
return null;
List<Type> typarams = t.getTypeArguments();
List<Type> typarams1 = rewriteTypeParams(path, typarams);
if (outer1 != outer || typarams != typarams1) {
t = new ClassType(outer1, typarams1, t.tsym, t.getMetadata());
}
return reannotate(t, path);
}

@Override
public Void visitWildcardType(
WildcardType t, List<TypeAnnotationPosition.TypePathEntry> path) {
visit(t.type, path.append(TypeAnnotationPosition.TypePathEntry.WILDCARD));
return super.visitWildcardType(t, path);
private List<Type> rewriteTypeParams(
List<TypeAnnotationPosition.TypePathEntry> path, List<Type> typarams) {
var i = IntStream.iterate(0, x -> x + 1).iterator();
return typarams.map(typaram -> visit(typaram,
path.append(new TypeAnnotationPosition.TypePathEntry(
TypeAnnotationPosition.TypePathEntryKind.TYPE_ARGUMENT, i.nextInt()))));
}

@Override
public Void visitArrayType(ArrayType t, List<TypeAnnotationPosition.TypePathEntry> path) {
visit(t.elemtype, path.append(TypeAnnotationPosition.TypePathEntry.ARRAY));
return super.visitArrayType(t, path);
public Type visitWildcardType(
WildcardType wt, List<TypeAnnotationPosition.TypePathEntry> path) {
Type t = wt.type;
if (t != null) {
t = visit(t, path.append(TypeAnnotationPosition.TypePathEntry.WILDCARD));
}
if (t != wt.type) {
wt = new WildcardType(t, wt.kind, wt.tsym, wt.bound, wt.getMetadata());
}
return reannotate(wt, path);
}

@Override
public Void visitType(Type t, List<TypeAnnotationPosition.TypePathEntry> path) {
ListBuffer<Attribute.TypeCompound> attributes = attributesByPath.remove(path);
if (attributes != null) {
attributesByType.put(t, attributes.toList());
public Type visitArrayType(ArrayType t, List<TypeAnnotationPosition.TypePathEntry> path) {
Type elemtype = t.elemtype;
Type elemtype1 =
visit(elemtype, path.append(TypeAnnotationPosition.TypePathEntry.ARRAY));
if (elemtype1 != elemtype) {
t = new ArrayType(elemtype1, t.tsym, t.getMetadata());
}
return null;
return reannotate(t, path);
}
}

/** A type mapping that rewrites the type to include type annotations. */
private static class TypeAnnotationTypeMapping extends Type.StructuralTypeMapping<Void> {

private final Map<Type, List<Attribute.TypeCompound>> attributesByType;

private TypeAnnotationTypeMapping(
Map<Type, List<Attribute.TypeCompound>> attributesByType) {
this.attributesByType = attributesByType;
@Override
public Type visitType(Type t, List<TypeAnnotationPosition.TypePathEntry> path) {
return reannotate(t, path);
}

private <T extends Type> Type reannotate(T t, BiFunction<T, Void, Type> f) {
// We're relying on object identify of Type instances to record where the annotations
// need to be added, so we have to retrieve the annotations for each type before
// rewriting it, and then add them after its contained types have been rewritten.
List<Attribute.TypeCompound> attributes = attributesByType.remove(t);
Type mapped = f.apply(t, null);
if (attributes == null) {
return mapped;
Type reannotate(Type type, List<TypeAnnotationPosition.TypePathEntry> path) {
List<Attribute.TypeCompound> attributes = attributesForPath(path);
if (attributes.isEmpty()) {
return type;
}
// Runtime-visible and -invisible annotations are completed separately, so if the same
// type has annotations from both it will get annotated twice.
TypeMetadata metadata = mapped.getMetadata();
TypeMetadata metadata = type.getMetadata();
TypeMetadata.Annotations existing =
(TypeMetadata.Annotations) metadata.get(TypeMetadata.Entry.Kind.ANNOTATIONS);
if (existing != null) {
TypeMetadata.Annotations combined = new TypeMetadata.Annotations(
existing.getAnnotations().appendList(attributes));
return mapped.cloneWithMetadata(
return type.cloneWithMetadata(
metadata.without(TypeMetadata.Entry.Kind.ANNOTATIONS).combine(combined));
}
return mapped.annotatedType(attributes);
}

@Override
public Type visitClassType(ClassType t, Void unused) {
return reannotate(t, super::visitClassType);
return type.annotatedType(attributes);
}

@Override
public Type visitWildcardType(WildcardType t, Void unused) {
return reannotate(t, super::visitWildcardType);
}

@Override
public Type visitArrayType(ArrayType t, Void unused) {
return reannotate(t, super::visitArrayType);
}

@Override
public Type visitType(Type t, Void unused) {
return reannotate(t, (x, u) -> x);
List<Attribute.TypeCompound> attributesForPath(
List<TypeAnnotationPosition.TypePathEntry> path) {
ListBuffer<Attribute.TypeCompound> attributes = attributesByPath.remove(path);
return attributes != null ? attributes.toList() : List.nil();
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2121,6 +2121,11 @@ compiler.warn.annotation.method.not.found=\
compiler.warn.annotation.method.not.found.reason=\
Cannot find annotation method ''{1}()'' in type ''{0}'': {2}

# 0: list of annotation, 1: symbol, 2: name, 3: message segment
compiler.err.cant.attach.type.annotations=\
Cannot attach type annotations {0} to {1}.{2}:\n\
{3}

# 0: file object, 1: symbol, 2: name
compiler.warn.unknown.enum.constant=\
unknown enum constant {1}.{2}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/*
* Copyright (c) 2024, Alphabet LLC. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/

/*
* @test
* @bug 8337998
* @summary CompletionFailure in getEnclosingType attaching type annotations
* @library /tools/javac/lib /tools/lib
* @modules jdk.compiler/com.sun.tools.javac.api
* jdk.compiler/com.sun.tools.javac.main
*/

import toolbox.*;
import toolbox.Task.*;

import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;

public class CompletionErrorOnEnclosingType {
ToolBox tb = new ToolBox();

public static void main(String... args) throws Exception {
CompletionErrorOnEnclosingType t = new CompletionErrorOnEnclosingType();
t.testMissingEnclosingType();
}

void testMissingEnclosingType() throws Exception {
String annoSrc =
"""
import static java.lang.annotation.ElementType.TYPE_USE;
import java.lang.annotation.Target;
@Target(TYPE_USE)
@interface Anno {}

class A<E> {}

class B {
private @Anno A<String> a;
}
""";
String cSrc =
"""
class C {
B b;
}
""";

Path base = Paths.get(".");
Path src = base.resolve("src");
tb.createDirectories(src);
tb.writeJavaFiles(src, annoSrc, cSrc);
Path out = base.resolve("out");
tb.createDirectories(out);
new JavacTask(tb).outdir(out).files(tb.findJavaFiles(src)).run();

// now if we remove A.class there will be an error but javac should not crash
tb.deleteFiles(out.resolve("A.class"));
List<String> log =
new JavacTask(tb)
.outdir(out)
.classpath(out)
.options("-XDrawDiagnostics")
.files(src.resolve("C.java"))
.run(Expect.FAIL)
.writeAll()
.getOutputLines(Task.OutputKind.DIRECT);

var expectedOutput =
List.of(
"B.class:-:-: compiler.err.cant.attach.type.annotations: @Anno, B, a,"
+ " (compiler.misc.class.file.not.found: A)",
"1 error");
if (!expectedOutput.equals(log)) {
throw new Exception("expected output not found: " + log);
}
}
}
1 change: 1 addition & 0 deletions test/langtools/tools/javac/diags/examples.not-yet.txt
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ compiler.err.signature.doesnt.match.intf # UNUSED
compiler.err.signature.doesnt.match.supertype # UNUSED
compiler.err.source.cant.overwrite.input.file
compiler.err.stack.sim.error
compiler.err.cant.attach.type.annotations # bad class file
compiler.err.type.var.more.than.once # UNUSED
compiler.err.type.var.more.than.once.in.result # UNUSED
compiler.err.unexpected.type
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -585,4 +585,8 @@ class Inner90<@TA(90) T> {
@Test(posn=4, annoType = TB.class, expect = "100")
class Inner100<T extends Inner100<@TB(100) T>> {
}

@Test(posn=1, annoType=TA.class, expect="130")
@Test(posn=23, annoType=TA.class, expect="131")
public Map<@TA(130) String, @TA(131) String> f130;
}