Skip to content

Diagnose intersection-bound annotation homogenization; add disabled regression test#1875

Draft
wmdietl wants to merge 2 commits into
masterfrom
fix-intersection-bound-annotations
Draft

Diagnose intersection-bound annotation homogenization; add disabled regression test#1875
wmdietl wants to merge 2 commits into
masterfrom
fix-intersection-bound-annotations

Conversation

@wmdietl

@wmdietl wmdietl commented Jul 17, 2026

Copy link
Copy Markdown
Member

Diagnosis of the intersection-bound annotation homogenization bug, with a disabled regression test. No production change is included — the one-line fix exists but flips deliberate Nullness Checker test expectations, so it needs a maintainer semantics decision first. Draft until that decision is made.

Diagnosis

AnnotatedIntersectionType.copyIntersectionBoundAnnotations() (AnnotatedTypeMirror.java:2982) is documented as bounds → primary, but its final addAnnotations(annos) hits the intersection's addAnnotation override, which calls fixupBoundAnnotations() — and that writes the primary back onto every bound via replaceAnnotations. So the first bound's qualifier (per hierarchy) is round-tripped onto all bounds: a differently-qualified bound is overwritten and an unannotated bound loses its default. First-bound-wins is exactly the order-dependence observed through the JSpecify reference checker, and it is visible today in framework/tests/lubglb/IntersectionTypes.java (call1/call2: same bounds, opposite order, only one flagged).

Both construction paths funnel through it: TypeParamElementAnnotationApplier.applyUpperBounds (line 178) and TypeFromTypeTreeVisitor.visitTypeParameter (line 232). No cache or defaulting path is involved.

The candidate fix, and why it is not landed

Adding the collected annotations to the primary only (bypassing the writeback, e.g. via super.addAnnotation) fixes the regression test and :framework:test (after updating 3 test expectations), but :checker:NullnessTest fails with 4 flipped diagnostics: Issue3349.java:8 and Issue868.java:42/43/48. Issue868.java is a deliberate spec of the homogenized behavior, including the explicit.annotation.ignored advisory from BaseTypeVisitor.checkExplicitAnnotationsOnIntersectionBounds, which encodes the homogenization contract. The JSpecify case (unannotated bound should default) is structurally identical to Issue868 test2/test3 (which accept homogenization), so no mechanical fix satisfies both — it is a defaulting/subtyping policy decision for intersection bounds.

Landing the fix would require updating Issue868.java, Issue3349.java, MultiBoundTypeVar.java, lubglb/IntersectionTypes.java, and reconsidering checkExplicitAnnotationsOnIntersectionBounds.

Full write-up (including the candidate patch) in cf-tasks/task-2-findings.md in the local workspace.

This PR contains

  • framework/tests/disabled-intersection-bounds/IntersectionBoundAnnos.java — EvenOdd-checker input asserting per-bound behavior, with an order-dependence guard (@Odd bound first vs. second).
  • DisabledIntersectionBoundAnnosTest.java@Ignored runner pointing at the diagnosis.

Tests: :framework:test and :checker:NullnessTest pass unmodified (the disabled test compiles but is skipped).

🤖 Generated with Claude Code

https://claude.ai/code/session_01EaHhVqLoJukm4kKsE7UfY4

…nization

AnnotatedIntersectionType.copyIntersectionBoundAnnotations summarizes the
bounds' annotations into the primary location via addAnnotations, whose
intersection override calls fixupBoundAnnotations and copies that primary back
onto every bound. An unannotated bound thus loses its default qualifier and a
differently-qualified bound is overwritten, order-dependently, homogenizing the
per-bound annotations that downstream checkers (e.g. the JSpecify reference
checker) need.

The narrowest fix (add to the primary location only, bypassing the writeback)
is a single line, but it changes intersection-bound defaulting and subtyping
semantics that the standard Nullness Checker relies on: checker/tests/nullness
Issue868.java and Issue3349.java deliberately encode the homogenized behavior,
including the explicit.annotation.ignored advisory from
BaseTypeVisitor.checkExplicitAnnotationsOnIntersectionBounds. Reconciling that
with per-bound preservation is a semantics decision beyond a contained bug fix,
so the fix is not landed here.

Commit the regression test as an @ignore'd JUnit class plus its EvenOdd-checker
input, documenting the desired per-bound behavior. See cf-tasks/task-2-findings.md
for the full diagnosis, instrumentation evidence, and proposed patch.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant