#1655 addresses one issue regarding captured types. Here are some more suggested by Codex that we should address.
Captured-Type Handling Audit
This audit identified one high-confidence correctness gap and several narrower gaps in NullAway's
handling of javac CapturedType instances.
Findings
High confidence: wildcard containment checks
CheckIdenticalNullabilityVisitor inconsistently recognizes wildcards. extendsBoundContains()
uses GenericsUtils.asWildcard(), which includes captured wildcards, but the surrounding logic and
superWildcardContains() use instanceof WildcardType or TypeKind.WILDCARD.
Captured wildcards can therefore bypass containment and nullability checks, especially for
? super cases.
Relevant code:
nullaway/src/main/java/com/uber/nullaway/generics/CheckIdenticalNullabilityVisitor.java
around typeArgumentContainedBy() and superWildcardContains().
Medium confidence: nested annotation restoration
TypeSubstitutionUtils.RestoreNullnessAnnotationsVisitor handles other instanceof WildcardType
but not captured wildcards. The new visitCapturedType() override fixes restoration of direct
annotations, but nested annotations in a captured wildcard's bound can still be lost.
Relevant code:
nullaway/src/main/java/com/uber/nullaway/generics/TypeSubstitutionUtils.java
around visitClassType(), visitWildcardType(), and visitCapturedType().
Medium confidence: library-model type paths
AddAnnotationToNestedTypeVisitor has no captured-type override. A library-model type path that
reaches a captured wildcard bound falls through to visitType() and cannot traverse that bound.
Relevant code:
nullaway/src/main/java/com/uber/nullaway/librarymodel/AddAnnotationToNestedTypeVisitor.java.
Diagnostic-only: captured-type pretty printing
GenericTypePrettyPrintingVisitor.visitCapturedType() prints only the capture's underlying
wildcard. It discards direct synthetic nullability annotations attached to the captured type,
potentially hiding the important type distinction in error messages.
Relevant code:
nullaway/src/main/java/com/uber/nullaway/generics/GenericTypePrettyPrintingVisitor.java.
Known limitation: captured array types
CheckIdenticalNullabilityVisitor.visitArrayType() explicitly accepts cases where the right-hand
type is not an array, including captured array types, without checking nested nullability. The
existing issue1503CapturedArrayTypeInTernary test verifies only that NullAway does not crash, so
false negatives remain possible.
Recommended Follow-up
- Address
CheckIdenticalNullabilityVisitor first by consistently normalizing wildcards and
captured wildcards through GenericsUtils.asWildcard().
- Add focused tests for captured
extends and super containment before changing behavior.
- Investigate how to preserve both direct and bound annotations when reconstructing a javac
CapturedType.
- Add library-model tests whose nested annotation paths encounter capture conversion.
- Update captured-type pretty printing once the internal representation rules are settled.
NestedTypeVarSubstitutionRepairVisitor and TypeVarWithSymbolCollector appear appropriately
handled for their current roles.
#1655 addresses one issue regarding captured types. Here are some more suggested by Codex that we should address.
Captured-Type Handling Audit
This audit identified one high-confidence correctness gap and several narrower gaps in NullAway's
handling of javac
CapturedTypeinstances.Findings
High confidence: wildcard containment checks
CheckIdenticalNullabilityVisitorinconsistently recognizes wildcards.extendsBoundContains()uses
GenericsUtils.asWildcard(), which includes captured wildcards, but the surrounding logic andsuperWildcardContains()useinstanceof WildcardTypeorTypeKind.WILDCARD.Captured wildcards can therefore bypass containment and nullability checks, especially for
? supercases.Relevant code:
nullaway/src/main/java/com/uber/nullaway/generics/CheckIdenticalNullabilityVisitor.javaaround
typeArgumentContainedBy()andsuperWildcardContains().Medium confidence: nested annotation restoration
TypeSubstitutionUtils.RestoreNullnessAnnotationsVisitorhandlesother instanceof WildcardTypebut not captured wildcards. The new
visitCapturedType()override fixes restoration of directannotations, but nested annotations in a captured wildcard's bound can still be lost.
Relevant code:
nullaway/src/main/java/com/uber/nullaway/generics/TypeSubstitutionUtils.javaaround
visitClassType(),visitWildcardType(), andvisitCapturedType().Medium confidence: library-model type paths
AddAnnotationToNestedTypeVisitorhas no captured-type override. A library-model type path thatreaches a captured wildcard bound falls through to
visitType()and cannot traverse that bound.Relevant code:
nullaway/src/main/java/com/uber/nullaway/librarymodel/AddAnnotationToNestedTypeVisitor.java.Diagnostic-only: captured-type pretty printing
GenericTypePrettyPrintingVisitor.visitCapturedType()prints only the capture's underlyingwildcard. It discards direct synthetic nullability annotations attached to the captured type,
potentially hiding the important type distinction in error messages.
Relevant code:
nullaway/src/main/java/com/uber/nullaway/generics/GenericTypePrettyPrintingVisitor.java.Known limitation: captured array types
CheckIdenticalNullabilityVisitor.visitArrayType()explicitly accepts cases where the right-handtype is not an array, including captured array types, without checking nested nullability. The
existing
issue1503CapturedArrayTypeInTernarytest verifies only that NullAway does not crash, sofalse negatives remain possible.
Recommended Follow-up
CheckIdenticalNullabilityVisitorfirst by consistently normalizing wildcards andcaptured wildcards through
GenericsUtils.asWildcard().extendsandsupercontainment before changing behavior.CapturedType.NestedTypeVarSubstitutionRepairVisitorandTypeVarWithSymbolCollectorappear appropriatelyhandled for their current roles.