Skip to content

Test capture glb with a non-top type-parameter bound#1876

Open
wmdietl wants to merge 2 commits into
masterfrom
fix-capture-ub-qualifiers
Open

Test capture glb with a non-top type-parameter bound#1876
wmdietl wants to merge 2 commits into
masterfrom
fix-capture-ub-qualifiers

Conversation

@wmdietl

@wmdietl wmdietl commented Jul 17, 2026

Copy link
Copy Markdown
Member

Investigation of the report that capture conversion drops a wildcard's more-precise qualifier from the capture's upper bound (observed through the JSpecify reference checker). Verdict: stock CF is correct — no framework change is warranted. This PR adds the test coverage that proves it.

Diagnosis

Instrumenting annotateCapturedTypeVar (AnnotatedTypeFactory.java:6196) and TypeVariableSubstitutor showed:

  • annotateCapturedTypeVar does compute annotatedGLB(typeVarUpperBound, wildcard.getExtendsBound()), and the per-hierarchy glb returns the correct, more-precise side (typeVarUB=@NTDTop Object, wildcardExtends=@NTDMiddle BarglbUB=@NTDMiddle Bar).
  • In the JSpecify repro itself the finished capture is fully correct (upper bound carries the nonnull qualifier). The wildcard's extends bound reaches capture undefaulted, so this is also not a downstream instance of the intersection-bound homogenization from Diagnose intersection-bound annotation homogenization; add disabled regression test #1875.
  • The qualifier is dropped after capture, by the reference checker's NullSpecAnnotatedTypeFactory.substituteTypeVariable override: stock CF only overrides on the use's primary annotation (empty for a bare T), while the override additionally applies the bound's effective unspecified operator, clobbering the capture's precise qualifier. The sample failure pattern keys on the parameter's bound qualifier, consistent with substitution, not capture.

Follow-up therefore belongs in the reference checker (skip the effective-annotation clause when the argument is a captured type variable with a more precise bound), not in CF.

This PR contains

framework/tests/nontopdefault/CaptureGlbWildcardBound.java — a passing regression test filling the gap explicitly noted in h1h2checker/WildcardBounds.java (wildcard capture against a non-top type-parameter bound, where the glb is not trivially the wildcard's bound). Includes a discriminating control: ? extends @NTDTop Bar must not be assignable to @NTDMiddle, proving the glb varies with the wildcard's extends bound.

(The cf-tasks/task-3-findings.md referenced in the comments is the full diagnosis write-up in the local task workspace, not in this repo.)

Tests: :framework:test (including the new test) and :checker:NullnessTest pass.

🤖 Generated with Claude Code

https://claude.ai/code/session_01EaHhVqLoJukm4kKsE7UfY4

framework/tests/h1h2checker/WildcardBounds.java notes it only exercises
wildcard capture against a top type-parameter bound ("We could add an
OuterS1 to also test with a non-top upper bound."), for which the glb of
the wildcard's extends bound and the parameter bound trivially equals the
wildcard's bound.

Add a nontopdefault test capturing Foo<? extends Bar> where
Foo<T extends @NTDTop Object> and the wildcard's extends bound (the
@NTDMiddle type-use default, or an explicit qualifier) is strictly more
precise than the @NTDTop parameter bound. This confirms the captured type
variable's upper bound is the greatest lower bound (@NTDMiddle), not the
parameter bound wholesale, and that the result varies with the wildcard's
extends bound (an explicit @NTDTop bound is not assignable to @NTDMiddle).

This is the CF-level reduction for cf-tasks/task-3; see
cf-tasks/task-3-findings.md. Capture conversion computes the glb
correctly in stock CF, so no framework change is warranted.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 17, 2026 19:19

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

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.

2 participants