Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,22 @@
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/** Report all uses of a type that has this annotation. Can also be used on a package. */
/**
* Report all uses of a type that has this annotation. Can also be used on a package.
*
* <p>When written on a package, {@code @ReportUse} applies to that package and its subpackages by
* default. Set {@link #applyToSubpackages()} to false to limit it to the package itself; doing so
* does not block an applicable {@code @ReportUse} on an enclosing package.
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.PACKAGE, ElementType.TYPE})
public @interface ReportUse {}
public @interface ReportUse {

/**
* When used on a package, whether this annotation should also apply to subpackages.
*
* @return whether this annotation should be inherited by subpackages
*/
boolean applyToSubpackages() default true;
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@
* warnings. However, a class with a relevant {@code @AnnotatedFor} annotation is always defaulted
* normally (typically using the CLIMB-to-top rule), and typechecking warnings are issued.
*
* <p>An {@code @AnnotatedFor} on a package also applies to subpackages, unless the {@code
* applyToSubpackages} field is set to false. Setting it to false does not block an applicable
* {@code @AnnotatedFor} on an enclosing package.
*
* @checker_framework.manual #compiling-libraries Compiling partially-annotated libraries
*/
@Documented
Expand All @@ -39,4 +43,11 @@
* @checker_framework.manual #shorthand-for-checkers Short names for built-in checkers
*/
String[] value();

/**
* When used on a package, whether this annotation should also apply to subpackages.
*
* @return whether this annotation should be inherited by subpackages
*/
boolean applyToSubpackages() default true;
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,11 @@
* <h2>Written on a package</h2>
*
* <p>When {@code @HasQualifierParameter} is written on a package, it is equivalent to writing that
* annotation on each class in the package or in a sub-package. It can be disabled on a specific
* class and its subclasses by writing {@code @NoQualifierParameter} on that class. This annotation
* may not be written on the same class as {@code NoQualifierParameter} for the same hierarchy.
* annotation on each class in the package or in a sub-package. Set the {@code applyToSubpackages}
* field to false to limit it to the package itself; doing so does not block an applicable
* {@code @HasQualifierParameter} on an enclosing package. It can be disabled on a specific class
* and its subclasses by writing {@code @NoQualifierParameter} on that class. This annotation may
* not be written on the same class as {@code @NoQualifierParameter} for the same hierarchy.
*
* @see NoQualifierParameter
*/
Expand All @@ -81,4 +83,11 @@
* @return the value
*/
Class<? extends Annotation>[] value();

/**
* When used on a package, whether this annotation should also apply to subpackages.
*
* @return whether this annotation should be inherited by subpackages
*/
boolean applyToSubpackages() default true;
}
9 changes: 9 additions & 0 deletions checker/jtreg/subpackages/AnnotatedForNested.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/*
* @test
* @summary An AnnotatedFor with applyToSubpackages=false limits only its own annotation. An
* enclosing package whose annotation applies to subpackages still reaches through it, so code in
* the nested subpackage is checked rather than given conservative defaults.
*
* @compile/fail/ref=AnnotatedForNested.out -XDrawDiagnostics -processor org.checkerframework.checker.nullness.NullnessChecker -AuseConservativeDefaultsForUncheckedCode=source,bytecode af/package-info.java af/sub/package-info.java af/sub/deep/Deep.java
*/
public class AnnotatedForNested {}
4 changes: 4 additions & 0 deletions checker/jtreg/subpackages/AnnotatedForNested.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Deep.java:15:14: compiler.err.proc.messager: [argument.type.incompatible] incompatible argument for parameter nn of Deep.take.
found : @Nullable Object
required: @NonNull Object
1 error
8 changes: 8 additions & 0 deletions checker/jtreg/subpackages/HasQualifierParameterNested.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/*
* @test
* @summary A HasQualifierParameter with applyToSubpackages=false limits only its own annotation.
* An enclosing package whose annotation applies to subpackages still reaches through it.
*
* @compile -processor org.checkerframework.checker.tainting.TaintingChecker -Werror hqp/package-info.java hqp/sub/package-info.java hqp/sub/deep/Deep.java
*/
public class HasQualifierParameterNested {}
4 changes: 4 additions & 0 deletions checker/jtreg/subpackages/af/package-info.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
@AnnotatedFor("nullness")
package af;

import org.checkerframework.framework.qual.AnnotatedFor;
17 changes: 17 additions & 0 deletions checker/jtreg/subpackages/af/sub/deep/Deep.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package af.sub.deep;

import org.checkerframework.checker.nullness.qual.Nullable;

/**
* Package af.sub sets applyToSubpackages=false, which limits its own annotation to af.sub. It does
* not block package af, whose annotation applies to subpackages and so still reaches here. This
* code is therefore inside an AnnotatedFor scope and its warnings are issued; if the walk up the
* package chain stopped at af.sub, conservative defaults would suppress them.
*/
public class Deep {
void take(Object nn) {}

void m(@Nullable Object nble) {
take(nble);
}
}
4 changes: 4 additions & 0 deletions checker/jtreg/subpackages/af/sub/package-info.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
@AnnotatedFor(value = "nullness", applyToSubpackages = false)
package af.sub;

import org.checkerframework.framework.qual.AnnotatedFor;
5 changes: 5 additions & 0 deletions checker/jtreg/subpackages/hqp/package-info.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
@HasQualifierParameter(Tainted.class)
package hqp;

import org.checkerframework.checker.tainting.qual.Tainted;
import org.checkerframework.framework.qual.HasQualifierParameter;
13 changes: 13 additions & 0 deletions checker/jtreg/subpackages/hqp/sub/deep/Deep.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package hqp.sub.deep;

import org.checkerframework.checker.tainting.qual.PolyTainted;

/**
* Package hqp.sub sets applyToSubpackages=false, which limits its own annotation to hqp.sub. It
* does not block package hqp, whose annotation applies to subpackages and so still reaches here.
* The class therefore has a qualifier parameter and the polymorphic qualifier is allowed; if the
* walk up the package chain stopped at hqp.sub, this would be invalid.polymorphic.qualifier.use.
*/
public class Deep {
@PolyTainted int field;
}
5 changes: 5 additions & 0 deletions checker/jtreg/subpackages/hqp/sub/package-info.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
@HasQualifierParameter(value = Tainted.class, applyToSubpackages = false)
package hqp.sub;

import org.checkerframework.checker.tainting.qual.Tainted;
import org.checkerframework.framework.qual.HasQualifierParameter;
9 changes: 9 additions & 0 deletions checker/tests/tainting/hqpoptin/InNestedSubpackage.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package hqpoptin.sub.nested;

import org.checkerframework.checker.tainting.qual.PolyTainted;

// applyToSubpackages defaults to true, so the qualifier parameter reaches transitively nested
// subpackages.
public class InNestedSubpackage {
@PolyTainted int field;
}
9 changes: 9 additions & 0 deletions checker/tests/tainting/hqpoptin/InSubpackage.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package hqpoptin.sub;

import org.checkerframework.checker.tainting.qual.PolyTainted;

// applyToSubpackages defaults to true, so this class inherits the qualifier parameter from package
// hqpoptin and the polymorphic qualifier is allowed.
public class InSubpackage {
@PolyTainted int field;
}
7 changes: 7 additions & 0 deletions checker/tests/tainting/hqpoptin/package-info.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// This file and the class beside it are deliberately in one directory so that the package
// annotation and the subpackage class are compiled together.
@HasQualifierParameter(Tainted.class)
package hqpoptin;

import org.checkerframework.checker.tainting.qual.Tainted;
import org.checkerframework.framework.qual.HasQualifierParameter;
9 changes: 9 additions & 0 deletions checker/tests/tainting/hqpoptout/InNestedSubpackage.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package hqpoptout.sub.nested;

import org.checkerframework.checker.tainting.qual.PolyTainted;

// The enclosing package opts out of all subpackages, including transitively nested ones.
public class InNestedSubpackage {
// :: error: (invalid.polymorphic.qualifier.use)
@PolyTainted int field;
}
9 changes: 9 additions & 0 deletions checker/tests/tainting/hqpoptout/InPackage.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package hqpoptout;

import org.checkerframework.checker.tainting.qual.PolyTainted;

// The package's @HasQualifierParameter still covers the package itself, so a polymorphic
// qualifier may be written on this field.
public class InPackage {
@PolyTainted int field;
}
10 changes: 10 additions & 0 deletions checker/tests/tainting/hqpoptout/InSubpackage.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package hqpoptout.sub;

import org.checkerframework.checker.tainting.qual.PolyTainted;

// Package hqpoptout sets applyToSubpackages=false, so this class has no qualifier parameter and
// the polymorphic qualifier is rejected.
public class InSubpackage {
// :: error: (invalid.polymorphic.qualifier.use)
@PolyTainted int field;
}
7 changes: 7 additions & 0 deletions checker/tests/tainting/hqpoptout/package-info.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// This file and the classes beside it are deliberately in one directory so that the package
// annotation and the subpackage class are compiled together.
@HasQualifierParameter(value = Tainted.class, applyToSubpackages = false)
package hqpoptout;

import org.checkerframework.checker.tainting.qual.Tainted;
import org.checkerframework.framework.qual.HasQualifierParameter;
19 changes: 18 additions & 1 deletion docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,13 @@ alternative to running it as a standalone annotation processor. It is published
`io.github.eisop:framework-errorprone` and requires JDK 21 or later. See the manual's
"Error Prone" section.

`AnnotatedFor`, `HasQualifierParameter`, and `ReportUse` gain the
`applyToSubpackages` element that `DefaultQualifier` already had. It says whether
an annotation written on a package also applies to that package's subpackages,
and defaults to `true`, so existing code is unaffected. Setting it to false limits
only that annotation; an applicable annotation on an enclosing package still
applies.

The Checker Framework now issues an `annotation.on.supertype` error when an annotation supported by
the checker is written as a main annotation on the superclass or interface in an `extends` or
`implements` clause. Annotations on the supertype's type arguments remain permitted. A checker
Expand Down Expand Up @@ -408,6 +415,15 @@ through fix-carrying overloads, which are `private`. Host-side interception of
diagnostics is done by installing a `DiagnosticSink`, not by overriding
`printOrStoreMessage`.

Code that walks up the package chain looking for a package annotation must now gate
each step to an enclosing package on that annotation's `applyToSubpackages` element;
the annotated package itself is always in scope. There are two new methods for this:
`AnnotationUtils.appliesToSubpackages(AnnotationMirror, ExecutableElement)`, and
`AnnotatedTypeFactory.doesAnnotatedForApplyToSubpackages(AnnotationMirror)` for
`@AnnotatedFor`. A null element, as in a `checker-qual` that predates it, is treated
as true, so a package annotation from such an artifact applies to subpackages as it
always did.

`AnnotatedIntersectionType.summarizeBounds` computes the summary described
above, reading each bound's qualifier, explicit or defaulted, uniformly,
and folding
Expand Down Expand Up @@ -765,7 +781,8 @@ eisop#104, eisop#386, eisop#433, eisop#622, eisop#737, eisop#778, eisop#786,
eisop#792, eisop#863, eisop#949, eisop#1015, eisop#1059, eisop#1074, eisop#1244,
eisop#1315, eisop#1564, eisop#1592, eisop#1642, eisop#1653, eisop#1735,
eisop#1801, eisop#1818, eisop#1819, eisop#1861, eisop#1862, eisop#1863,
eisop#1865, eisop#1887, eisop#1965, eisop#1987, typetools#399, typetools#3203.
eisop#1865, eisop#1887, eisop#1965, eisop#1987, eisop#1990, typetools#399,
typetools#3203.


Version 3.49.5-eisop1 (April 26, 2026)
Expand Down
4 changes: 3 additions & 1 deletion docs/manual/advanced-features.tex
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,9 @@

If \code{@DefaultQualifier}[\code{s}] is placed on a package (via the
\<package-info.java> file), then it applies to the given package \emph{and}
all subpackages.
all subpackages. To limit a default to the package it is written on, set
the \<applyToSubpackages> element to false, as in
\<@DefaultQualifier(value = NonNull.class, applyToSubpackages = false)>.
% This is slightly at odds with Java's treatment of packages of different
% names as essentially unrelated, but is more intuitive and useful.

Expand Down
11 changes: 11 additions & 0 deletions docs/manual/annotating-libraries.tex
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,17 @@
any annotations, but that you examined the source code and verified
that all appropriate annotations are present.

\<@AnnotatedFor> may also be written on a package, via the
\<package-info.java> file, in which case it applies to the given package
\emph{and} all subpackages. To limit it to the package it is written on,
set the \<applyToSubpackages> element to false, as in
\<@AnnotatedFor(value = "nullness", applyToSubpackages = false)>.
This limits only that annotation; it does not block an applicable
\<@AnnotatedFor> on an enclosing package.
Because \<@AnnotatedFor> has source retention, a package annotation only
affects compilation units that are compiled together with its
\<package-info.java> file.

\begin{sloppypar}
Whenever you compile a class using the Checker Framework, including when
using the \<-AuseConservativeDefaultsForUncheckedCode=source,bytecode> command-line
Expand Down
6 changes: 6 additions & 0 deletions docs/manual/generics.tex
Original file line number Diff line number Diff line change
Expand Up @@ -1051,6 +1051,12 @@
package mypackage;
\end{Verbatim}

To limit \<@HasQualifierParameter> to the package it is written on, set the
\<applyToSubpackages> element to false, as in
\<@HasQualifierParameter(value = Tainted.class, applyToSubpackages = false)>.
This limits only that annotation; it does not block an applicable
\<@HasQualifierParameter> on an enclosing package.

When using \<@HasQualifierParameter> on a package, it's possible to disable it
for a specific class using \refqualclass{framework/qual}{NoQualifierParameter}.
Writing this on a class indicates it has no class qualifier parameter and
Expand Down
8 changes: 8 additions & 0 deletions framework/jtreg/subpackages/ReportUseNested.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/*
* @test
* @summary A ReportUse with applyToSubpackages=false limits only its own annotation. An enclosing
* package whose annotation applies to subpackages still reaches through it.
*
* @compile/fail/ref=ReportUseNested.out -XDrawDiagnostics -processor org.checkerframework.common.util.report.ReportChecker ru/package-info.java ru/sub/package-info.java ru/sub/deep/Deep.java
*/
public class ReportUseNested {}
3 changes: 3 additions & 0 deletions framework/jtreg/subpackages/ReportUseNested.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Deep.java:8:8: compiler.err.proc.messager: [usage] Usage of ru [PACKAGE] by ru.sub.deep.Deep [CLASS]
Deep.java:8:8: compiler.err.proc.messager: [usage] Usage of ru [PACKAGE] by ru.sub.deep.Deep [CLASS]
2 errors
4 changes: 4 additions & 0 deletions framework/jtreg/subpackages/ru/package-info.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
@ReportUse
package ru;

import org.checkerframework.common.util.report.qual.ReportUse;
8 changes: 8 additions & 0 deletions framework/jtreg/subpackages/ru/sub/deep/Deep.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package ru.sub.deep;

/**
* Package ru.sub sets applyToSubpackages=false, which limits its own annotation to ru.sub. It does
* not block package ru, whose annotation applies to subpackages and so still reaches here, so this
* class is reported.
*/
public class Deep {}
4 changes: 4 additions & 0 deletions framework/jtreg/subpackages/ru/sub/package-info.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
@ReportUse(applyToSubpackages = false)
package ru.sub;

import org.checkerframework.common.util.report.qual.ReportUse;
Loading
Loading