Introduce @UnannotatedFor - #856
Merged
Merged
Conversation
@UnAnnnotatedFor
Collaborator
Author
|
TODO: change Retention for both annotatedfor and unannotatedfor qualifiers. |
Keep only the introduction of @UnannotatedFor. Conflicts between @AnnotatedFor and @UnannotatedFor on the same element are left for a separate change, so drop what this branch had toward them: * SourceChecker: revert to master. Its only effect was to let an @UnannotatedFor override an @AnnotatedFor on the same element; for every other element it returned what master already returned, and no test covers it. It also declared an unused ANNOTATEDFOR_CONFLICT message key with no entry in any messages.properties, and iterated both annotation arrays after a null check that only required one of them to be non-null. * QualifierDefaults: remove a commented-out copy of isElementUnannotatedForThisChecker and restore a Javadoc typo. The only merge conflict was an import in AnnotatedTypeFactory.
Conflict in QualifierDefaults: eisop#1331 removed isElementAnnotatedForThisChecker and its elementAnnotatedFors cache from QualifierDefaults, so this branch's changes to that method are obsolete. Take master's side; the @UnannotatedFor logic is ported to the shared lookup in a follow-up commit. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…or scope @UnannotatedFor was defined and given an AnnotatedTypeFactory helper, but its effect was implemented in QualifierDefaults.isElementAnnotatedForThisChecker, which eisop#1331 removed. Implement it in the lookup that replaced it, BaseTypeChecker.isElementAnnotatedForThisCheckerOrUpstreamChecker, so that it governs both conservative defaults and warning suppression. @AnnotatedFor is tested first, so @UnannotatedFor is purely subtractive: it stops the walk to the enclosing element, and never overrides an explicit @AnnotatedFor on the same element. A nested @AnnotatedFor takes effect again. Both shouldSuppressWarnings overloads accumulated the @AnnotatedFor answer over every enclosing declaration, and the TreePath overload additionally asked about the enclosing package. That defeated the exclusion: an @UnannotatedFor class in an @AnnotatedFor package was defaulted as unchecked code but still had its warnings reported. Ask isElementAnnotatedForThisCheckerOrUpstreamChecker once, about the innermost declaration; it already resolves enclosing scopes, so the per-level query was redundant work even before this change. Only one cache is needed. The element's cached boolean is the fully-resolved scope answer, including @UnannotatedFor exclusions. Also mirror doesUnannotatedForApplyToThisChecker on its @AnnotatedFor sibling, and correct the @UnannotatedFor javadoc, which claimed an effect on bytecode although the annotation has source retention. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Resolve the conflicts that eisop#1996 (package annotation subpackage opt-outs) and eisop#1998 (checker modes) create with @UnannotatedFor: - BaseTypeChecker: eisop#1996 replaced the enclosing-package step of isElementAnnotatedForThisCheckerOrUpstreamChecker with a walk that honors applyToSubpackages, and @UnannotatedFor has to stop that walk too. The walk is now tri-state (PackageScope), so the innermost package annotation wins whichever of the two it is. - AnnotatedTypeFactory: keep both the applyToSubpackages element of @AnnotatedFor and the value element of @UnannotatedFor, and add the applyToSubpackages element of @UnannotatedFor. - @UnannotatedFor gains the applyToSubpackages element that eisop#1996 added to @AnnotatedFor, @HasQualifierParameter and @ReportUse. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Fourteen findings from a review of the `@UnannotatedFor` branch. The two that mattered most were regressions this branch introduced: - `AnnotatedTypeFactory` resolved `@UnannotatedFor`'s elements with `TreeUtils.getMethod`, which throws a `UserError` when the type is absent. `@UnannotatedFor` is EISOP-specific, so an upstream typetools `checker-qual` on the classpath aborted every checker before any file was checked, where that configuration previously worked with a degraded `applyToSubpackages`. `getMethodOrNull` is not a fix: it routes through the same `getMethods`. - `@NullUnmarked` undid only the `@AnnotatedFor` half of `@NullMarked`, so a type variable of a `@NullUnmarked` method was still bounded by `@NonNull`. Also: hoist a fast-path guard in both `SourceChecker.shouldSuppressWarnings` overloads, which run for every reported diagnostic and are provably constant `false` when neither `-AuseConservativeDefaultsForUncheckedCode=source` nor `-AonlyAnnotatedFor` is set; correct the `@UnannotatedFor` and `isElementAnnotatedForThisCheckerOrUpstreamChecker` javadoc; and add tests for the conservative-defaulting half of `@UnannotatedFor`, which no test had observed from outside an excluded scope. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Reconcile @UnannotatedFor with three changes that landed upstream: - eisop#1304 (@NullMarked aliasing) solves the subchecker-divergence problem that this branch had solved by gating the alias on -Amode=jspecify. Its fix is better: alias to @AnnotatedFor("nullnessnoinit"), so the alias's reach matches the factory it is registered on, in every mode. Drop the gating and give @NullUnmarked the same treatment: @UnannotatedFor("nullnessnoinit", applyToSubpackages = false), plus the @DefaultQualifier half that undoes an enclosing @NullMarked's upper-bound default. - eisop#2041 (@AnnotatedFor repeatable) replaced the single getDeclAnnotation call in BaseTypeChecker with a loop over getAnnotatedForAnnotations. Do the same for @UnannotatedFor, in both the element walk and the subpackage walk. - eisop#2040 removed the deprecated org.jspecify.nullness package, so drop the legacy aliases this branch had added for it. Follow eisop#2041's guard style in AnnotatedTypeFactory: name UnannotatedFor and UnannotatedFor.List with literal strings rather than class literals, so a classpath without them is detected instead of linking them. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The merge conflict resolution inserted the `@UnannotatedFor` sentence into warnings.tex's `-AonlyAnnotatedFor` section twice, and left the `NullnessChecker.addOptionsForMode` javadoc describing this branch's earlier, superseded design, in which `-Amode=jspecify` alone made `@NullMarked` and `@NullUnmarked` scope annotations and the alias named the "nullness" checker. eisop#1304 aliases to "nullnessnoinit" in every mode instead. Drop the duplicate sentence, and restore the javadoc to master's wording. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`getUnannotatedForAnnotations` collects written and aliased annotations together so that a written `@UnannotatedFor` for another checker cannot hide the `@NullUnmarked` alias, but no test exercised that composition; a regression to a single `getDeclAnnotation` call would have passed every test. The manual also called `@UnannotatedFor` the inverse of `@AnnotatedFor` without saying that stub files ignore it, or which one wins when both are on one element. Add the composition case to NullUnmarkedScope.java, document both points, and fix the `NullnessUnannotatedForTest` javadoc, which named the wrong class. Also collapse the tail of both `shouldSuppressWarnings` overloads: after the fast path returns early when neither flag is set, the `else if` on those flags was always true and the final `return false` was unreachable. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`:framework:checkSignature` rejected the `@FullyQualifiedName` local that held `UnannotatedFor.List`'s name, because it was built by concatenating ".List" onto the `UnannotatedFor` name, and the Signature Checker types a concatenation as `@SignatureUnknown`. Write the name as a literal, as is already done for `UnannotatedFor` and for `AnnotatedFor.List` just above. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This branch added the JSpecify jar to the jtreg javac classpath so that the NullUnmarkedPackage jtreg test could resolve `org.jspecify.annotations`. The change was never needed: jtreg's javac classpath already includes the checker shadow jar, which bundles the JSpecify annotations. With master's build.gradle, all 116 checker jtreg tests pass, and NullUnmarkedPackage fails with exactly its expected nullness diagnostic rather than an unresolved import. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The release section lists changes newest-first, but this branch's two entries were added long ago and every PR merged since has stacked its entry above them, leaving them in the middle of the section. Move them to the top, unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Brings in eisop#2022 (resolve aliases in checks that read annotations as written). That change is about type-qualifier aliases and does not touch the declaration-alias path that `@UnannotatedFor` and `@NullUnmarked` use, but it made `AnnotatedTypeFactory.getAllDeclAnnotations` private and documented `getAnnotatedForAnnotations` as its only caller. `getUnannotatedForAnnotations` is a second caller in the same class, so the access change needs nothing, and the javadoc now names both. The only textual conflict was the release's "Closed issues" list; keep both sides' issue numbers. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
aosen-xiong
marked this pull request as ready for review
September 11, 2026 23:34
…nnotatedFor` `getAnnotatedForAnnotations` and `getUnannotatedForAnnotations` were the same method apart from the names they look up: collect the written and aliased annotations, then unpack the `@Repeatable` container. `getAllDeclAnnotations`, already private with only those two callers, now takes the container's name and `value` element and does the unpacking, so each public method is one call. Naming the container rather than passing its class also removes the last two `.List` class literals: a class literal loads the type, which is what the constructor's string names exist to avoid. Both names become constants, so the constructor and the lookup share one literal. Also rename `namesThisChecker` to `appliesToThisChecker`, matching the public methods it serves, group the four `does...` predicates by the question they answer, and move the null-element guard into `appliesToThisChecker` so that both `@UnannotatedFor` predicates read like their `@AnnotatedFor` twins. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The walk returned a three-valued `PackageScope` on the grounds that a boolean could not distinguish "an `@UnannotatedFor` excludes the subpackages" from "nothing found here, keep looking". It can: in a recursive walk, "keep looking" is the recursive call, not a returned value. Each package either decides and returns, or hands back its parent's answer unchanged, so no level ever needs to tell those two apart. Restore master's boolean `doesAnnotatedForReachSubpackages`, its cache and its name, and add one early return for an `@UnannotatedFor` that reaches subpackages. `checker/jtreg/subpackages/UnannotatedForNested` fails without that early return, so it is what makes the nested case work. Also merge `isElementUnannotatedForThisChecker` and `excludesSubpackages`, which were the same loop apart from one conjunct, into one method taking a `requireSubpackages` flag. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ent diagnostics `shouldSuppressWarnings(Element, String)` had to change with the `TreePath` overload, or a diagnostic reported on an element would escape an `@UnannotatedFor` exclusion through its three callers: a report on an element, `InitializationVisitor`'s list of uninitialized fields, and whole-program inference. Nothing tested it: reverting that overload to master's logic passed every test. Extend `ElementSuppressionTestCase`, whose test checker reports on class elements, with an excluded class and a sibling that is still checked. Document which defaults apply in an `@UnannotatedFor` scope, which the manual did not say: the conservative defaults under `-AuseConservativeDefaultsForUncheckedCode=source`, the ordinary defaults under `-AonlyAnnotatedFor` (which only suppresses warnings), and, in both cases, an enclosing `@DefaultQualifier` still wins over the conservative defaults for the hierarchy and locations it names --- including the one `@NullMarked` stands for. Also record in the changelog that `@NullUnmarked`, like `@NullMarked`, is retained in class files and so applies to bytecode, and correct a fixture comment copied from the `@AnnotatedFor` opt-out test, where the same words describe the opposite outcome. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Fixes eisop#876. Writing both annotations for one checker on a single declaration contradicts itself. The framework resolved it silently, in favor of `@AnnotatedFor`, so the `@UnannotatedFor` had no effect and nothing said so. Report `conflicting.annotatedfor` instead; the precedence still applies if the warning is suppressed. Only a checker named by both annotations warns, so `@AnnotatedFor("nullness") @UnannotatedFor("regex")` is silent. The warning is deduplicated through the ultimate parent checker: "nullness" matches the upstream chain of `NullnessChecker`, `NullnessNoInitSubchecker` and `KeyForSubchecker`, each of which has its own visitor and would otherwise report the same conflict. Restricting it to the parent checker instead would miss `@NullMarked`/`@NullUnmarked`, which resolve only in the nullness subchecker's factory. A package is reached through a class in it, since a `package-info.java` declares no type and the type processor never visits it; the warning is reported on the package element, so it is positioned at the package declaration rather than at the class that led to it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
aosen-xiong
added a commit
to aosen-xiong/jdk
that referenced
this pull request
Sep 11, 2026
Keeps this repo's copy byte-identical to eisop/checker-framework's, per `checker/bin-devel/check-jdk-consistency.sh`. eisop/checker-framework#856 adds to the javadoc that writing both an `@AnnotatedFor` and an `@UnannotatedFor` naming the same checker on one declaration is a `conflicting.annotatedfor` warning, and that the `@AnnotatedFor` wins if it is suppressed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Only the two documentation files conflicted, both because master and this branch each added an entry at the top of the same section. Keep both. In nullness-checker.tex, master still carries "`@NullUnmarked` is not yet honored", which this branch makes false; drop that sentence and keep both its replacement and master's new `-AjspecifyUnrecognizedLocations` item (eisop#2024). eisop#2044 reworks how a package's `@DefaultQualifier` propagates to subpackages, which is the mechanism the `@NullMarked`/`@NullUnmarked` aliases rely on, both being `applyToSubpackages = false` on the same location and hierarchy. The three behaviors it could have disturbed are each covered and pass: a `@NullMarked` package not covering its subpackages, `@NullUnmarked` undoing the upper-bound default, and `@NullUnmarked` on a package. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The test covered no flag, `-AonlyAnnotatedFor` and `-AuseConservativeDefaultsForUncheckedCode=source`, but not the mode that `@UnannotatedFor` exists to serve. Add that run, as `AnnotatedForWithUse` already does. It gets its own reference file rather than sharing the `-AonlyAnnotatedFor` one, matching `AnnotatedForWithUse`, whose two differ: its JSpecify-mode run also reports `jspecify.unrecognized.location.local`. The two files have the same content here only because this fixture annotates no local variable. Regenerate the three existing reference files as well: the added `@compile` line shifts every line number in the file. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`processClassTree` reached the package through every class in it, and only the report was deduplicated, so the package's `getAnnotatedForAnnotations` --- a declaration-annotation scan, an alias-map lookup and a `.List` lookup --- ran once per class rather than once per package, as the comment beside it claimed. The check is not behind the flag fast path that guards scope resolution, so this ran on every compilation. Record the packages already examined, separately from the checker-wide record of what has been reported: that one stops a second checker from repeating the warning, this one stops the lookups from being repeated at all. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`BaseTypeChecker` and `BaseTypeVisitor` had five copies of the same loop: iterate an element's `@AnnotatedFor` or `@UnannotatedFor` annotations --- which may be several, since both are repeatable and an alias such as `@NullMarked` adds another --- and ask whether any applies to this checker, in two of the five also requiring that it apply to subpackages. Replace them with `hasApplicableAnnotatedFor` and `hasApplicableUnannotatedFor` on `BaseTypeChecker`, each taking a `requireSubpackages` flag. Both callers are in this package, so neither needs to be public. `hasApplicable...` rather than `is...ForThisChecker`, which read like `AnnotatedTypeFactory`'s `does...ApplyToThisChecker`: those ask about one annotation, these about an element. The two comments that explained the loops move to the javadoc, including why both conditions must hold of the same annotation. `BaseTypeChecker.getTypeFactory()` returns the visitor's own factory, so moving the loops off the visitor does not change which factory resolves aliases. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The subpackages tests added by this PR followed the existing layout of that directory: a top-level driver naming an abbreviated fixture package, with all fixture trees flat beside each other. With five more tests the correspondence between a driver and its fixtures stopped being evident from the listing -- UnannotatedForOptOutNested to uafoptout, NullUnmarkedPackage to nup. Put each new test in its own directory holding its driver, its .out file and its fixtures, as checker/jtreg/unboundedWildcards/issue1275, checker/jtreg/stubs/fakeoverrides and framework/jtreg/issue845 already do. The directory then carries the test's identity, so the fixture root is uniformly `pkg` and fixture classes are named for the package they are in: InPkg, InSub, Deep, Deeper. The tests that predate this PR are left alone. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Member
|
#1482 updates the |
# Conflicts: # docs/CHANGELOG.md # framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java
Make UNANNOTATED_FOR_NAME protected in AnnotatedTypeFactory and use it instead of UnannotatedFor.class in NullnessNoInitAnnotatedTypeFactory. Also guard the applyToSubpackages element on nullUnmarkedUnannotatedForBuilder with unannotatedForApplyToSubpackagesElement != null, matching the pattern used for AnnotatedFor and DefaultQualifier.
Collaborator
Author
An element carrying both, each naming this checker, was resolved by a fixed precedence: the @AnnotatedFor won and the @UnannotatedFor was ignored. That disagrees with how the same contradiction is resolved for @DefaultQualifier, which the previous release made an error resolved by source order, and the disagreement is reachable because one annotation can supply both kinds at once: JSpecify's @NullUnmarked aliases to an @UnannotatedFor and to a @DefaultQualifier(Nullable, UPPER_BOUND). Writing @NullUnmarked before @NullMarked therefore produced a declaration that was in scope for checking, because the @AnnotatedFor half won by fixed precedence, but whose upper-bound default came from @NullUnmarked, because the @DefaultQualifier half won by source order. Neither annotation produces that combination on its own. Under -AonlyAnnotatedFor the class was checked with unmarked defaults. Resolve the pair by source order too, so both halves of such an alias agree. AnnotatedTypeFactory#annotatedForPrecedesUnannotatedFor makes one ordered pass over getDeclAnnotations and reports which kind it reaches first, counting an aliasing annotation at its own position and a @repeatable container at the position of its first repeat, as the @DefaultQualifier resolution does. It runs only for an element carrying both, so the common path is unchanged. Reword conflicting.annotatedfor, which claimed the @UnannotatedFor "has no effect": the annotation that loses here may still supply defaults that apply. Also make the two new Element-keyed sets identity-keyed, as the neighboring caches in BaseTypeChecker are and as the JDK documents Element requires; move BaseTypeVisitor's new field up with the other fields; and note on annotatedForReachesSubpackagesCache that its value now accounts for @UnannotatedFor. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… wins Resolving the pair by source order made the warning about it reachable only in one of the two orders. The warning goes through shouldSuppressWarnings like any other diagnostic, and under -AonlyAnnotatedFor or -AuseConservativeDefaultsForUncheckedCode=source that method suppresses everything outside an @AnnotatedFor scope. When the @UnannotatedFor is written first it wins, the declaration is outside the scope, and the contradiction was reported nowhere -- silently, and only for the order that the previous commit changed. Exempt conflicting.annotatedfor from the scope-based suppression: a diagnostic about the @AnnotatedFor/@UnannotatedFor pair itself must not be silenced by the scope those annotations define. An explicit @SuppressWarnings, tested earlier in the same method, still silences it. Update conflictingAnnotatedForPackage's expected output for the reworded message; its own resolution is unchanged, since its @AnnotatedFor is written first. Add conflictingUnannotatedForPackage for the other order, where the package is left unchecked and only the warning is reported. That pair is what distinguishes source order from a fixed precedence: with @AnnotatedFor always winning, the new test's package would be checked and its argument would be an error. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
getAllDeclAnnotations walked the element's declaration annotations inline and then called getDeclAnnotation for the @repeatable container, which walks the same set again for the container's name and a third time for its aliases, recomputing each mirror's name as it goes. Handle both in the one loop, as getDefaultQualifierAnnotations does for @DefaultQualifier and its container. This matters more now than when the method served @AnnotatedFor alone: BaseTypeVisitor calls it for every class and every method through the conflicting-annotation check, and it is called a second time per element for @UnannotatedFor. No behavior change: javac exposes either the individual annotations or the container, never both, and the result is a set. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #1292.
Merge together with eisop/jdk#144, which adds the checker-qual copy of
@UnannotatedForunderjava.basein the companion JDK repo: without it,check-jdk-consistency.shfails withFile missing in JDK: framework/qual/UnannotatedFor.java. Both branches are namedjspecify-eisopso CI's related-repo checkout matches them automatically.Summary
@UnannotatedForis the inverse of@AnnotatedFor: it subtracts a declaration from an enclosing@AnnotatedForscope, so a package or class can be checked as a whole while individual members opt out. It mirrors@AnnotatedForin every respect — same@Target(TYPE,METHOD,CONSTRUCTOR,PACKAGE), same retention,@Repeatablewith the same container shape, and anapplyToSubpackageselement with the same meaning.The Nullness Checker aliases JSpecify's
@NullUnmarkedto it, alongside a@DefaultQualifier(Nullable.class, locations = UPPER_BOUND)that restores the unmarked upper-bound default. Without that second half, an enclosing@NullMarked's@DefaultQualifierwould keep applying and a type variable of a@NullUnmarkedmethod would still be bounded by@NonNull.Resolving a declaration that carries both
Writing an
@AnnotatedForand an@UnannotatedForthat name the same checker is a contradiction. The one written first wins, and aconflicting.annotatedforwarning is issued on the declaration.Source order rather than a fixed precedence, because one annotation can supply both kinds at once:
@NullUnmarkedaliases to an@UnannotatedForand to a@DefaultQualifier, and conflicting@DefaultQualifiers are already resolved by source order (#2066). Were the two halves resolved by different rules,@NullUnmarked @NullMarkedwould leave the declaration in scope for checking while its upper-bound default came from the annotation that lost — a state neither annotation produces on its own, and one that-AonlyAnnotatedFormakes observable.An aliasing annotation counts at its own position and a
@Repeatablecontainer at the position of its first repeat, matching the@DefaultQualifierresolution. The ordered pass runs only for a declaration carrying both, so the common path is unchanged.conflicting.annotatedforis exempt from@AnnotatedFor-scope suppression. Otherwise, when the@UnannotatedForwins the declaration falls outside the scope and-AonlyAnnotatedForor-AuseConservativeDefaultsForUncheckedCode=sourcewould discard the warning — leaving the contradiction unreported in exactly one of its two orders. An explicit@SuppressWarningsstill silences it.Mechanism
AnnotatedTypeFactory.getUnannotatedForAnnotationsis the@UnannotatedForcounterpart ofgetAnnotatedForAnnotations. Both delegate togetAllDeclAnnotations, which was generalized to take the@Repeatablecontainer's name andvalueelement, so the container unpacking is shared rather than duplicated; it now collects the annotation and its container in a single pass over the element's declaration annotations.doesAnnotatedForApplyToThisCheckerand its@UnannotatedForcounterpart shareappliesToThisChecker.@UnannotatedForis EISOP-specific, so the whole annotation is absent when the classpath resolvesorg.checkerframework.framework.qualfrom upstream typetoolschecker-qual. Its names are literal strings rather than class literals so referring to them does not link the class, and the resolved elements are null in that case, which makes every lookup return empty.Known limitations
AbstractTypeProcessordrops theANALYZEevent for apackage-info.java, so no checker can visit a package declaration directly — see AbstractTypeProcessor drops the ANALYZE event for package-info.java/module-info.java, so BaseTypeVisitor checkers can never visit them #1958, whose fix would let this check reach the package on its own and let the workaround here be removed.@UnannotatedForinherits@AnnotatedFor'sSOURCEretention, so it has no effect on already-compiled code.@AnnotatedForruntime retention #1482 changes that for both. The@NullUnmarkedalias is unaffected, since JSpecify retains that annotation at runtime.Tests
jtreg coverage for nesting, subpackage opt-in/opt-out, repeatability, and the
@NullUnmarkedalias, under no flag,-AonlyAnnotatedFor,-AuseConservativeDefaultsForUncheckedCode=source, and-Amode=jspecify.conflictingAnnotatedForPackageandconflictingUnannotatedForPackagecover both orders of the contradiction on a package; the second is what distinguishes source order from a fixed precedence, since under the latter its package would be checked and its argument would be an error.🤖 Generated with Claude Code
https://claude.ai/code/session_012n7eKNuv1S2mTden7x56ie