Repository navigation
Check an annotated anonymous class creation against the declaration bound on all JDKs - #2027
Merged
Merged
Conversation
…ound on Java 11 "new @A AClass() {}" is validated against AClass's declaration bound on Java 17 and later but not on Java 11, so the same source checks differently depending on the compiler. eisop#1909 unified how anonymous classes are recognized but this path was left behind. Two things block it on Java 11, and neither alone changes the result: TreeUtils.isTypeTree answered from the tree kind alone, which the TODO it carried admitted is an under-approximation. BaseTypeValidator uses it to decide whether to validate a type that is syntactically an expression, and on Java 11 the extends clause of an annotated anonymous class is a bare IdentifierTree, so validation was skipped. Resolve the ambiguity by asking what the identifier, or member select, refers to. The clause also carries no annotation on Java 11: javac attaches it to the anonymous class body's modifiers rather than to the clause, a difference getExplicitNewClassAnnos already reconciles. Apply those annotations to the clause's type in getTypeOfExtendsImplements, after addComputedTypeAnnotations, whose defaulting would otherwise overwrite them. The annotation therefore lands on the extends clause, which is the second of the two desugarings in eisop#2010. That issue conditions it on whether the type system permits annotations on supertypes; this change does not consult that condition. Fixes eisop#2009. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
applyAnonymousClassCreationAnnos guarded the TreePath and then dereferenced getParentPath() unguarded. An extends or implements clause always has a parent, so the crash is unreachable, but the asymmetry reads as an oversight; AnnotatedTypeFactory:4801 and :4807 guard the same call. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
# Conflicts: # docs/CHANGELOG.md
# Conflicts: # docs/CHANGELOG.md
…type AnonymousClassBounds.java covered a plain and a qualified extends clause (IDENTIFIER and MEMBER_SELECT), but not an implements clause or a PARAMETERIZED_TYPE clause. The parameterized case is the only shape that isolates the AnnotatedTypeFactory half of the fix from the TreeUtils.isTypeTree half: PARAMETERIZED_TYPE is already one of typeTreeKinds() on every JDK, so only the annotation-reconciliation part is exercised there. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019jLjeEW1F1aE2E3ax7EWqV
The CHANGELOG and a code comment said the annotation-placement quirk was a Java 11 problem, checked correctly starting on Java 17. Probing javac's own AST directly, across every JDK from 8 to 21, shows the actual boundary is JDK 12: 8 through 11 attach the annotation to the anonymous class body's modifiers, 12 through 21 attach it to the extends/implements clause directly. Java 17 was never the boundary; nothing changed between 12 and 17 for this. Also mark the reconciliation code in getExplicitNewClassAnnos as removable once the minimum supported JDK is 12, since at that point it becomes a no-op that only ever contributes an empty annotation list. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019jLjeEW1F1aE2E3ax7EWqV
…nnos This file's convention for a void method that computes annotations and stamps them onto an AnnotatedTypeMirror is "add*", even when it does so via replaceAnnotations: addAnnotationFromFieldInvariant is the same shape (void, computes annotations, calls type.replaceAnnotations(...)) and is named with "add". "apply*" in this file is otherwise reserved for a method that returns a transformed type (applyCaptureConversion), which is not this method's shape. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019jLjeEW1F1aE2E3ax7EWqV
wmdietl
approved these changes
Sep 10, 2026
wmdietl
left a comment
Member
There was a problem hiding this comment.
Thanks! Please look at my refactorings.
Collaborator
Author
|
Thanks! This looks good. |
wmdietl
enabled auto-merge (squash)
September 11, 2026 20:12
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #2009.
new @A AClass() {}is validated againstAClass's declaration bound on Java 17 and later but noton Java 11, so the same source checks differently depending on the compiler. #1909 unified how
anonymous classes are recognized, but this path was left behind.
Two things block it on Java 11, and neither alone changes the result:
TreeUtils.isTypeTreeanswered from the tree kind alone, which the TODO it carried admitted is anunder-approximation.
BaseTypeValidatoruses it to decide whether to validate a type that issyntactically an expression, and on Java 11 the extends clause of an annotated anonymous class is a
bare identifier, so validation was skipped. It now resolves what the identifier, or member select,
refers to. The member-select case has no test: an annotated qualified type is
Outer.@Anno Inner,which arrives as an
ANNOTATED_TYPEand was already handled, so only the unannotated form reachesthat branch and it has no annotation to validate.
The clause also carries no annotation on Java 11: javac attaches it to the anonymous class body's
modifiers rather than to the clause, a difference
getExplicitNewClassAnnosalready reconciles.getTypeOfExtendsImplementsapplies those annotations to the clause's type, afteraddComputedTypeAnnotations, whose defaulting would otherwise overwrite them.Note for reviewers:
isTypeTreeis public APITreeUtils.isTypeTreeis public and its contract changes here. It was a pure kind test, answerablefor any tree; it now resolves an identifier or member select through
elementFromTree, so itrequires an attributed tree. A caller that passes an unattributed tree gets a different answer than
before.
Inside this repository there are exactly two callers, and only one is affected:
GenericAnnotatedTypeFactory.getAnnotatedTypeLhscalls it in thedefault:branch of a switchon the tree kind.
IDENTIFIERandMEMBER_SELECTare handled by earlier cases in that switch andnever reach
default:, so the new behaviour cannot be observed there.BaseTypeValidator.shouldCheckTopLevelDeclaredOrPrimitiveTypeis the intended target, and iswhere the anonymous-class fix takes effect.
🤖 Generated with Claude Code