Skip to content

Further address issues involving captured types #1656

Description

@msridhar

#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

  1. Address CheckIdenticalNullabilityVisitor first by consistently normalizing wildcards and
    captured wildcards through GenericsUtils.asWildcard().
  2. Add focused tests for captured extends and super containment before changing behavior.
  3. Investigate how to preserve both direct and bound annotations when reconstructing a javac
    CapturedType.
  4. Add library-model tests whose nested annotation paths encounter capture conversion.
  5. Update captured-type pretty printing once the internal representation rules are settled.

NestedTypeVarSubstitutionRepairVisitor and TypeVarWithSymbolCollector appear appropriately
handled for their current roles.

Metadata

Metadata

Assignees

No one assigned

    Labels

    jspecifyRelated to support for jspecify standard (see jspecify.dev)

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions