diff --git a/checker/jtreg/nullness/issue6374/Lib.java b/checker/jtreg/nullness/issue6374/Lib.java new file mode 100644 index 00000000000..dea871dac3a --- /dev/null +++ b/checker/jtreg/nullness/issue6374/Lib.java @@ -0,0 +1,17 @@ +import org.checkerframework.checker.nullness.qual.Nullable; +import org.jmlspecs.annotation.NonNull; + +@SuppressWarnings("unchecked") // ignore heap pollution +class Lib { + // element type inferred, array non-null + static void none(T... o) {} + + // element type inferred, array non-null + static void decl(@NonNull T... o) {} + + // element type nullable, array non-null + static void type(@Nullable T... o) {} + + // element type nullable, array nullable + static void typenn(@Nullable T @Nullable ... o) {} +} diff --git a/checker/jtreg/nullness/issue6374/User.java b/checker/jtreg/nullness/issue6374/User.java new file mode 100644 index 00000000000..ca0de77a7c2 --- /dev/null +++ b/checker/jtreg/nullness/issue6374/User.java @@ -0,0 +1,20 @@ +/* + * @test + * @summary Test case for typetools issue #6374: https://github.com/typetools/checker-framework/issues/6374 + * + * @compile Lib.java + * @compile/fail/ref=User.out -XDrawDiagnostics -Anomsgtext -processor org.checkerframework.checker.nullness.NullnessChecker User.java + */ +// Also see checker/tests/nullness/generics/Issue6374.java +class User { + void go() { + Lib.decl("", null); + // :: error: (argument.type.incompatible) + Lib.decl((Object[]) null); + Lib.type("", null); + // :: error: (argument.type.incompatible) + Lib.type((Object[]) null); + Lib.typenn("", null); + Lib.typenn((Object[]) null); + } +} diff --git a/checker/jtreg/nullness/issue6374/User.out b/checker/jtreg/nullness/issue6374/User.out new file mode 100644 index 00000000000..a80316facf4 --- /dev/null +++ b/checker/jtreg/nullness/issue6374/User.out @@ -0,0 +1,3 @@ +User.java:13:18: compiler.err.proc.messager: (argument.type.incompatible) +User.java:16:18: compiler.err.proc.messager: (argument.type.incompatible) +2 errors diff --git a/checker/tests/nullness/generics/Issue6374.java b/checker/tests/nullness/generics/Issue6374.java new file mode 100644 index 00000000000..199a46d9ffc --- /dev/null +++ b/checker/tests/nullness/generics/Issue6374.java @@ -0,0 +1,37 @@ +// Test case for typetools issue #6374: +// https://github.com/typetools/checker-framework/issues/6374 +// Also see checker/jtreg/nullness/issue6374/ + +import org.checkerframework.checker.nullness.qual.Nullable; +import org.jmlspecs.annotation.NonNull; + +public class Issue6374 { + + @SuppressWarnings("unchecked") // ignore heap pollution + static class Lib { + // element type inferred, array non-null + static void none(T... o) {} + + // element type inferred, array non-null + static void decl(@NonNull T... o) {} + + // element type nullable, array non-null + static void type(@Nullable T... o) {} + + // element type nullable, array nullable + static void typenn(@Nullable T @Nullable ... o) {} + } + + class User { + void go() { + Lib.decl("", null); + // :: error: (argument.type.incompatible) + Lib.decl((Object[]) null); + Lib.type("", null); + // :: error: (argument.type.incompatible) + Lib.type((Object[]) null); + Lib.typenn("", null); + Lib.typenn((Object[]) null); + } + } +} diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 353158481b6..9a10b33e6a4 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -7,6 +7,8 @@ Version 3.41.0-eisop2 (December ?, 2023) **Closed issues:** +typetools#6374. + Version 3.41.0-eisop1 (December 5, 2023) ---------------------------------------- diff --git a/framework/src/main/java/org/checkerframework/framework/util/element/ElementAnnotationUtil.java b/framework/src/main/java/org/checkerframework/framework/util/element/ElementAnnotationUtil.java index 6de1decbc27..c3d0055c426 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/element/ElementAnnotationUtil.java +++ b/framework/src/main/java/org/checkerframework/framework/util/element/ElementAnnotationUtil.java @@ -94,11 +94,16 @@ static void addDeclarationAnnotationsFromElement( // org.checkerframework.framework.type.TypeFromMemberVisitor.visitVariable AnnotatedTypeMirror innerType = AnnotatedTypes.innerMostType(type); if (innerType != type) { - for (AnnotationMirror annotation : annotations) { - if (AnnotationUtils.annotationName(annotation).startsWith("org.checkerframework")) { - innerType.addAnnotation(annotation); + for (AnnotationMirror anno : annotations) { + if (AnnotationUtils.isDeclarationAnnotation(anno) + // Always treat Checker Framework annotations as type annotations. + && !AnnotationUtils.annotationName(anno) + .startsWith("org.checkerframework")) { + // Declaration annotations apply to the outer type. + type.addAnnotation(anno); } else { - type.addAnnotation(annotation); + // Type annotations apply to the innermost type. + innerType.addAnnotation(anno); } } } else { diff --git a/framework/src/main/java/org/checkerframework/framework/util/element/TypeVarUseApplier.java b/framework/src/main/java/org/checkerframework/framework/util/element/TypeVarUseApplier.java index e0e117e2442..ec5e8e76656 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/element/TypeVarUseApplier.java +++ b/framework/src/main/java/org/checkerframework/framework/util/element/TypeVarUseApplier.java @@ -12,6 +12,7 @@ import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable; import org.checkerframework.framework.type.ElementAnnotationApplier; +import org.checkerframework.framework.util.AnnotatedTypes; import org.checkerframework.framework.util.element.ElementAnnotationUtil.UnexpectedAnnotationLocationException; import org.checkerframework.javacutil.BugInCF; @@ -47,6 +48,8 @@ public static void apply( * Returns true if type is an AnnotatedTypeVariable, or an AnnotatedArrayType with a type * variable component, and the element is not a TYPE_PARAMETER. * + * @param type the type to test + * @param element the corresponding element * @return true if type is an AnnotatedTypeVariable, or an AnnotatedArrayType with a type * variable component, and the element is not a TYPE_PARAMETER */ @@ -55,19 +58,15 @@ public static boolean accepts(AnnotatedTypeMirror type, Element element) { && ElementAnnotationUtil.contains(element.getKind(), acceptedKinds); } + /** + * Returns true if type is an array type whose innermost component type is a type variable. + * + * @param type the type to test + * @return true if type is an array type whose innermost component type is a type variable + */ private static boolean isGenericArrayType(AnnotatedTypeMirror type) { return type instanceof AnnotatedArrayType - && getNestedComponentType(type) instanceof AnnotatedTypeVariable; - } - - private static AnnotatedTypeMirror getNestedComponentType(AnnotatedTypeMirror type) { - - AnnotatedTypeMirror componentType = type; - while (componentType instanceof AnnotatedArrayType) { - componentType = ((AnnotatedArrayType) componentType).getComponentType(); - } - - return componentType; + && AnnotatedTypes.innerMostType(type) instanceof AnnotatedTypeVariable; } /** The generic array type, if any. */ @@ -108,7 +107,7 @@ private static AnnotatedTypeMirror getNestedComponentType(AnnotatedTypeMirror ty if (isGenericArrayType(type)) { this.arrayType = (AnnotatedArrayType) type; - this.typeVariable = (AnnotatedTypeVariable) getNestedComponentType(type); + this.typeVariable = (AnnotatedTypeVariable) AnnotatedTypes.innerMostType(type); this.declarationElem = (TypeParameterElement) typeVariable.getUnderlyingType().asElement(); this.useElem = element; @@ -131,10 +130,15 @@ private static AnnotatedTypeMirror getNestedComponentType(AnnotatedTypeMirror ty * @throws UnexpectedAnnotationLocationException if invalid location for an annotation was found */ public void extractAndApply() throws UnexpectedAnnotationLocationException { - ElementAnnotationUtil.addDeclarationAnnotationsFromElement( - typeVariable, useElem.getAnnotationMirrors()); + if (arrayType != null) { + ElementAnnotationUtil.addDeclarationAnnotationsFromElement( + arrayType, useElem.getAnnotationMirrors()); + } else { + ElementAnnotationUtil.addDeclarationAnnotationsFromElement( + typeVariable, useElem.getAnnotationMirrors()); + } - // apply declaration annotations + // apply annotations from the type parameter declaration ElementAnnotationApplier.apply(typeVariable, declarationElem, atypeFactory); List annotations = getAnnotations(useElem, declarationElem); @@ -145,7 +149,6 @@ public void extractAndApply() throws UnexpectedAnnotationLocationException { // are not applied as the type variables primary annotation typeVarAnnotations = removeComponentAnnotations(arrayType, annotations); ElementAnnotationUtil.annotateViaTypeAnnoPosition(arrayType, annotations); - } else { typeVarAnnotations = annotations; } @@ -155,27 +158,40 @@ public void extractAndApply() throws UnexpectedAnnotationLocationException { } } - private List removeComponentAnnotations( + /** + * Return the annotations that apply to the base component of the array and remove these + * annotations from the parameter. + * + * @param arrayType the array type + * @param annotations the annotations to inspect and modify + * @return the annotations that apply to the base component of the array + */ + private static List removeComponentAnnotations( AnnotatedArrayType arrayType, List annotations) { - List componentAnnotations = new ArrayList<>(); - if (arrayType != null) { - for (int i = 0; i < annotations.size(); ) { - Attribute.TypeCompound anno = annotations.get(i); - if (isBaseComponent(arrayType, anno)) { - componentAnnotations.add(anno); - annotations.remove(anno); - } else { - i++; - } + for (int i = 0; i < annotations.size(); ) { + Attribute.TypeCompound anno = annotations.get(i); + if (isBaseComponent(arrayType, anno)) { + componentAnnotations.add(anno); + annotations.remove(anno); + } else { + i++; } } return componentAnnotations; } - private boolean isBaseComponent(AnnotatedArrayType arrayType, Attribute.TypeCompound anno) { + /** + * Return true if anno applies to the base component of arrayType. + * + * @param arrayType the array type + * @param anno the annotation to inspect + * @return true if anno applies to the base component of arrayType + */ + private static boolean isBaseComponent( + AnnotatedArrayType arrayType, Attribute.TypeCompound anno) { try { return ElementAnnotationUtil.getTypeAtLocation(arrayType, anno.getPosition().location) .getKind() @@ -235,7 +251,6 @@ private static List getVariableAnnos(Element variableEle List annotations = new ArrayList<>(); for (Attribute.TypeCompound anno : varSymbol.getRawTypeAttributes()) { - TypeAnnotationPosition pos = anno.position; switch (pos.type) { case FIELD: