Background
The Java LST type model has no representation for packages. JavaType.Class.owningClass only stores enclosing FullyQualified (i.e. class-like) symbols (ReloadableJava21TypeMapping.java:229-231), and the javac/reflection type mappings never read PackageSymbol.getAnnotationMirrors() or Package.getDeclaredAnnotations(). As a result, package-level annotations declared in a dependency's package-info.class are not present anywhere in the LST.
This blocks nullability inference recipes that need to honor:
- JSpecify
@NullMarked / @NullUnmarked
- JSR-305
@ParametersAreNonnullByDefault / @FieldsAreNonnullByDefault
- FindBugs/SpotBugs default-scope annotations
- any other annotation that propagates from package to class to member
As a related symptom, even within source: FindMissingTypesTest.java:172-175 asserts that the type of @NullMarked on a package-info.java resolves to JavaType.Unknown — i.e. package annotations don't resolve their types even when the annotation source is the current project.
Proposal
Introduce JavaType.Package implements JavaType with fullyQualifiedName and annotations: FullyQualified[], accessed via a new @Nullable Package pkg field on JavaType.Class (plus a default getPackage() accessor on FullyQualified).
Scope
- New
JavaType.Package type, with constructor/unsafeSet and the @JsonTypeInfo machinery.
- Add
pkg field + getPackage() to JavaType.Class; extend unsafeSet overloads.
- Populate from javac (
ReloadableJava{8,11,17,21,25}TypeMapping), reflection (JavaReflectionTypeMapping — including a forced load of package-info.class so Package.getDeclaredAnnotations() returns non-empty), Kotlin (KotlinTypeMapping, KotlinIrTypeMapping), Scala (ScalaTypeMapping), and the source-side parser (ReloadableJava{N}ParserVisitor, which already collects getPackageAnnotations() syntactically).
- Signature builder: add
packageSignature(Object) to JavaTypeSignatureBuilder and all implementations; signature "<package> <fqn>" to keep it disjoint from class signatures.
- Visitor / RPC: add
visitPackage to JavaTypeVisitor, wire JavaTypeSender / JavaTypeReceiver.
- Flip
FindMissingTypesTest:172-175 from "expect Unknown" to "expect annotation type resolved".
- Convenience:
TypeUtils.findInheritedAnnotation(JavaType, String) that walks type-use → declaration → enclosing class → package.
Non-goals
- Module annotations (
module-info) — separate problem.
- Implementing nullability inference itself — this issue only unblocks it.
Compatibility
New optional field, additive. Existing recipes see no behavior change. RPC consumers running an older JavaTypeReceiver will fail on Package payloads — roll out the receiver change first, then the sender.
Acceptance criteria
- A class loaded from a JAR whose
package-info.class is annotated with @org.jspecify.annotations.NullMarked exposes that annotation via cls.getPackage().getAnnotations().
- Same for reflection-mapped classes.
- A
J.Package.annotations[0].getType() in a parsed package-info.java is no longer JavaType.Unknown.
- Round-trips correctly through RPC.
Background
The Java LST type model has no representation for packages.
JavaType.Class.owningClassonly stores enclosingFullyQualified(i.e. class-like) symbols (ReloadableJava21TypeMapping.java:229-231), and the javac/reflection type mappings never readPackageSymbol.getAnnotationMirrors()orPackage.getDeclaredAnnotations(). As a result, package-level annotations declared in a dependency'spackage-info.classare not present anywhere in the LST.This blocks nullability inference recipes that need to honor:
@NullMarked/@NullUnmarked@ParametersAreNonnullByDefault/@FieldsAreNonnullByDefaultAs a related symptom, even within source:
FindMissingTypesTest.java:172-175asserts that the type of@NullMarkedon apackage-info.javaresolves toJavaType.Unknown— i.e. package annotations don't resolve their types even when the annotation source is the current project.Proposal
Introduce
JavaType.Package implements JavaTypewithfullyQualifiedNameandannotations: FullyQualified[], accessed via a new@Nullable Package pkgfield onJavaType.Class(plus a defaultgetPackage()accessor onFullyQualified).Scope
JavaType.Packagetype, with constructor/unsafeSetand the@JsonTypeInfomachinery.pkgfield +getPackage()toJavaType.Class; extendunsafeSetoverloads.ReloadableJava{8,11,17,21,25}TypeMapping), reflection (JavaReflectionTypeMapping— including a forced load ofpackage-info.classsoPackage.getDeclaredAnnotations()returns non-empty), Kotlin (KotlinTypeMapping,KotlinIrTypeMapping), Scala (ScalaTypeMapping), and the source-side parser (ReloadableJava{N}ParserVisitor, which already collectsgetPackageAnnotations()syntactically).packageSignature(Object)toJavaTypeSignatureBuilderand all implementations; signature"<package> <fqn>"to keep it disjoint from class signatures.visitPackagetoJavaTypeVisitor, wireJavaTypeSender/JavaTypeReceiver.FindMissingTypesTest:172-175from "expect Unknown" to "expect annotation type resolved".TypeUtils.findInheritedAnnotation(JavaType, String)that walks type-use → declaration → enclosing class → package.Non-goals
module-info) — separate problem.Compatibility
New optional field, additive. Existing recipes see no behavior change. RPC consumers running an older
JavaTypeReceiverwill fail onPackagepayloads — roll out the receiver change first, then the sender.Acceptance criteria
package-info.classis annotated with@org.jspecify.annotations.NullMarkedexposes that annotation viacls.getPackage().getAnnotations().J.Package.annotations[0].getType()in a parsedpackage-info.javais no longerJavaType.Unknown.