4444import com .sun .tools .javac .code .Attribute ;
4545import com .sun .tools .javac .code .Flags ;
4646import com .sun .tools .javac .code .Symbol ;
47- import com .sun .tools .javac .code .TargetType ;
4847import com .sun .tools .javac .code .Type ;
49- import com .sun .tools .javac .code .TypeAnnotationPosition ;
50- import com .sun .tools .javac .code .TypeAnnotationPosition .TypePathEntry ;
5148import com .sun .tools .javac .code .TypeTag ;
5249import com .sun .tools .javac .code .Types ;
5350import com .sun .tools .javac .tree .JCTree ;
6259import java .util .stream .Stream ;
6360import javax .lang .model .element .AnnotationMirror ;
6461import javax .lang .model .element .AnnotationValue ;
65- import javax .lang .model .element .ElementKind ;
6662import javax .lang .model .element .ExecutableElement ;
6763import org .checkerframework .nullaway .javacutil .AnnotationUtils ;
6864import org .jspecify .annotations .Nullable ;
@@ -194,35 +190,6 @@ public static Stream<? extends AnnotationMirror> getAllAnnotations(Symbol symbol
194190 return Stream .concat (symbol .getAnnotationMirrors ().stream (), typeUseAnnotations );
195191 }
196192
197- /**
198- * Check if any direct annotation a symbol matches a given predicate. Includes code for backward
199- * compatibility with older JDKs where javac did not place type-use annotations on Symbols from
200- * bytecodes.
201- *
202- * @param symbol the symbol
203- * @param predicate the predicate to match annotation names against
204- * @return true if any annotation on the symbol matches the predicate, false otherwise
205- */
206- public static boolean hasAnyAnnotationMatchingBackCompat (
207- Symbol symbol , Predicate <String > predicate ) {
208- if (hasAnyAnnotationMatching (symbol , predicate )) {
209- return true ;
210- }
211- // to handle bytecodes, also check direct type-use annotations stored in attributes
212- Symbol typeAnnotationOwner =
213- symbol .getKind ().equals (ElementKind .PARAMETER ) ? symbol .owner : symbol ;
214- for (Attribute .TypeCompound typeCompound : typeAnnotationOwner .getRawTypeAttributes ()) {
215- if (!targetTypeMatches (symbol , typeCompound .position )
216- || !isDirectTypeUseAnnotation (typeCompound , symbol )) {
217- continue ;
218- }
219- if (predicate .test (typeCompound .getAnnotationType ().toString ())) {
220- return true ;
221- }
222- }
223- return false ;
224- }
225-
226193 /**
227194 * Check if any direct annotation a symbol matches a given predicate.
228195 *
@@ -237,14 +204,24 @@ public static boolean hasAnyAnnotationMatching(Symbol symbol, Predicate<String>
237204 return true ;
238205 }
239206 }
240- // check for type use annotations. For MethodSymbols, look on the return type
241- Type annotatedType =
242- symbol instanceof Symbol .MethodSymbol methodSymbol
243- ? methodSymbol .getReturnType ()
244- : symbol .type ;
245- for (AnnotationMirror annotationMirror : annotatedType .getAnnotationMirrors ()) {
246- if (predicate .test (annotationMirror .getAnnotationType ().toString ())) {
247- return true ;
207+ // Check for type-use annotations. For methods, look on the return type and its enclosing types,
208+ // since NullAway treats an annotation before the outer class of a nested return type as
209+ // applying to the full return type.
210+ if (symbol instanceof Symbol .MethodSymbol methodSymbol ) {
211+ for (Type currentType = methodSymbol .getReturnType ();
212+ currentType != null && !currentType .hasTag (TypeTag .NONE );
213+ currentType = currentType .getEnclosingType ()) {
214+ for (AnnotationMirror annotationMirror : currentType .getAnnotationMirrors ()) {
215+ if (predicate .test (annotationMirror .getAnnotationType ().toString ())) {
216+ return true ;
217+ }
218+ }
219+ }
220+ } else {
221+ for (AnnotationMirror annotationMirror : symbol .type .getAnnotationMirrors ()) {
222+ if (predicate .test (annotationMirror .getAnnotationType ().toString ())) {
223+ return true ;
224+ }
248225 }
249226 }
250227 return false ;
@@ -353,18 +330,11 @@ public static boolean hasAnyAnnotationMatching(Symbol symbol, Predicate<String>
353330 public static Stream <? extends AnnotationMirror > getAllAnnotationsForParameter (
354331 Symbol .MethodSymbol symbol , int paramInd ) {
355332 Symbol .VarSymbol varSymbol = symbol .getParameters ().get (paramInd );
356- // On modern javac versions, type-use annotations are attached directly to the parameter's
357- // type. Keep reading raw attributes as well for backward compatibility with older javac
358- // behavior.
359- Stream <? extends AnnotationMirror > typeUseAnnotations =
333+ Type parameterType = symbol .type .getParameterTypes ().get (paramInd );
334+ Stream <Attribute .TypeCompound > typeUseAnnotations =
360335 Stream .concat (
361- varSymbol .type .getAnnotationMirrors ().stream (),
362- symbol .getRawTypeAttributes ().stream ()
363- .filter (
364- t ->
365- t .position .type .equals (TargetType .METHOD_FORMAL_PARAMETER )
366- && t .position .parameter_index == paramInd
367- && NullabilityUtil .isDirectTypeUseAnnotation (t , symbol )))
336+ getTypeUseAnnotationsIncludingEnclosingTypes (varSymbol .type ),
337+ getTypeUseAnnotationsIncludingEnclosingTypes (parameterType ))
368338 .distinct ();
369339 return Stream .concat (varSymbol .getAnnotationMirrors ().stream (), typeUseAnnotations );
370340 }
@@ -374,137 +344,30 @@ public static Stream<? extends AnnotationMirror> getAllAnnotationsForParameter(
374344 * arguments, wildcards, etc.)
375345 */
376346 public static Stream <Attribute .TypeCompound > getTypeUseAnnotations (Symbol symbol ) {
377- return getTypeUseAnnotations (symbol , /* onlyDirect= */ true );
378- }
379-
380- /**
381- * Gets the type use annotations on a symbol
382- *
383- * @param symbol the symbol
384- * @param onlyDirect if true, only return annotations that are directly on the type, not on
385- * components of the type (type arguments, wildcards, array contents, etc.)
386- * @return the type use annotations on the symbol
387- */
388- private static Stream <Attribute .TypeCompound > getTypeUseAnnotations (
389- Symbol symbol , boolean onlyDirect ) {
390- // Adapted from Error Prone's MoreAnnotations class:
391- // https://github.com/google/error-prone/blob/5f71110374e63f3c35b661f538295fa15b5c1db2/check_api/src/main/java/com/google/errorprone/util/MoreAnnotations.java#L84-L91
392- Symbol typeAnnotationOwner =
393- symbol .getKind ().equals (ElementKind .PARAMETER ) ? symbol .owner : symbol ;
394- Stream <Attribute .TypeCompound > rawTypeAttributes =
395- typeAnnotationOwner .getRawTypeAttributes ().stream ();
347+ Type annotatedType =
348+ symbol instanceof Symbol .MethodSymbol methodSymbol
349+ ? methodSymbol .getReturnType ()
350+ : symbol .type ;
396351 if (symbol instanceof Symbol .MethodSymbol ) {
397- // for methods, we want annotations on the return type
398- return rawTypeAttributes .filter (
399- (t ) ->
400- t .position .type .equals (TargetType .METHOD_RETURN )
401- && (!onlyDirect || isDirectTypeUseAnnotation (t , symbol )));
402- } else {
403- // filter for annotations directly on the type
404- return rawTypeAttributes .filter (
405- t ->
406- targetTypeMatches (symbol , t .position )
407- && (!onlyDirect || isDirectTypeUseAnnotation (t , symbol )));
408- }
409- }
410-
411- // Adapted from Error Prone MoreAnnotations:
412- // https://github.com/google/error-prone/blob/5f71110374e63f3c35b661f538295fa15b5c1db2/check_api/src/main/java/com/google/errorprone/util/MoreAnnotations.java#L128
413- private static boolean targetTypeMatches (Symbol sym , TypeAnnotationPosition position ) {
414- switch (sym .getKind ()) {
415- case LOCAL_VARIABLE -> {
416- return position .type == TargetType .LOCAL_VARIABLE ;
417- }
418- case FIELD , ENUM_CONSTANT -> {
419- // treated like a field
420- return position .type == TargetType .FIELD ;
421- }
422- case CONSTRUCTOR , METHOD -> {
423- return position .type == TargetType .METHOD_RETURN ;
424- }
425- case PARAMETER -> {
426- if (position .type .equals (TargetType .METHOD_FORMAL_PARAMETER )) {
427- int parameterIndex = position .parameter_index ;
428- if (position .onLambda != null ) {
429- com .sun .tools .javac .util .List <JCTree .JCVariableDecl > lambdaParams =
430- position .onLambda .params ;
431- return parameterIndex >= 0
432- && parameterIndex < lambdaParams .size ()
433- && lambdaParams .get (parameterIndex ).sym .equals (sym );
434- } else {
435- return ((Symbol .MethodSymbol ) sym .owner ).getParameters ().indexOf (sym ) == parameterIndex ;
436- }
437- } else {
438- return false ;
439- }
440- }
441- case CLASS , ENUM , RECORD -> {
442- // treated like a class
443- // There are no type annotations on the top-level type of the class/enum/record being
444- // declared,
445- // only on other types in the signature (e.g. `class Foo extends Bar<@A Baz> {}`).
446- return false ;
447- }
448- default -> {
449- throw new AssertionError ("unsupported element kind " + sym .getKind () + " symbol " + sym );
450- }
352+ return getTypeUseAnnotationsIncludingEnclosingTypes (annotatedType );
451353 }
354+ return annotatedType .getAnnotationMirrors ().stream ();
452355 }
453356
454357 /**
455- * Check whether a type-use annotation should be treated as applying directly to the top-level
456- * type
358+ * Gets the type-use annotations directly on {@code type} or any of its enclosing types.
457359 *
458- * <p>For example {@code @Nullable List<T> lst} is a direct type use annotation of {@code lst},
459- * but {@code List<@Nullable T> lst} is not.
460- *
461- * @param t the annotation and its position in the type
462- * @param symbol the symbol for the annotated element
463- * @return {@code true} if the annotation should be treated as applying directly to the top-level
464- * type, false otherwise
360+ * <p>javac models an annotation written before the outer class in a nested type on that enclosing
361+ * type. For method return and parameter nullability, NullAway treats such an annotation as
362+ * applying to the full nested type.
465363 */
466- private static boolean isDirectTypeUseAnnotation (Attribute .TypeCompound t , Symbol symbol ) {
467- // location is a list of TypePathEntry objects, indicating whether the annotation is
468- // on an array, inner type, wildcard, or type argument. If it's empty, then the
469- // annotation is directly on the type.
470- // Annotations on array dimensions, wildcards, or type arguments do not apply to the top-level
471- // type. For nested classes, an annotation applies directly only when placed on the innermost
472- // type.
473- int innerTypeCount = 0 ;
474- for (TypePathEntry entry : t .position .location ) {
475- switch (entry .tag ) {
476- case INNER_TYPE -> {
477- innerTypeCount ++;
478- }
479- case ARRAY -> {
480- return false ;
481- }
482- default -> {
483- // Wildcard or type argument!
484- return false ;
485- }
486- }
487- }
488- // For non-nested classes annotations apply to the innermost type.
489- if (!isTypeOfNestedClass (symbol .type )) {
490- return true ;
491- }
492- // For nested classes the annotation is only valid if it is on the innermost type.
493- return innerTypeCount == getNestingDepth (symbol .type ) - 1 ;
494- }
495-
496- private static int getNestingDepth (Type type ) {
497- int depth = 0 ;
498- for (Type curr = type ;
499- curr != null && !curr .hasTag (TypeTag .NONE );
500- curr = curr .getEnclosingType ()) {
501- depth ++;
502- }
503- return depth ;
504- }
505-
506- private static boolean isTypeOfNestedClass (Type type ) {
507- return type .tsym != null && type .tsym .owner instanceof Symbol .ClassSymbol ;
364+ private static Stream <Attribute .TypeCompound > getTypeUseAnnotationsIncludingEnclosingTypes (
365+ Type type ) {
366+ Stream <Attribute .TypeCompound > annotations = type .getAnnotationMirrors ().stream ();
367+ Type enclosingType = type .getEnclosingType ();
368+ return enclosingType == null || enclosingType .hasTag (TypeTag .NONE )
369+ ? annotations
370+ : Stream .concat (annotations , getTypeUseAnnotationsIncludingEnclosingTypes (enclosingType ));
508371 }
509372
510373 /**
@@ -644,19 +507,17 @@ private static boolean checkArrayElementAnnotations(
644507 Config config ,
645508 BiPredicate <String , Config > typeUseCheck ,
646509 BiPredicate <Symbol , Config > declarationCheck ) {
647- if (getTypeUseAnnotations (arraySymbol , /* onlyDirect= */ false )
648- .anyMatch (
649- t -> {
650- // the location list should be of length 1 and the entry tag should be ARRAY
651- com .sun .tools .javac .util .List <TypePathEntry > location = t .position .location ;
652- TypePathEntry head = location .head ;
653- boolean singleElementList = head != null && location .tail .isEmpty ();
654- if (singleElementList && head .tag == TypeAnnotationPosition .TypePathEntryKind .ARRAY ) {
655- return typeUseCheck .test (t .type .toString (), config );
656- }
657- return false ;
658- })) {
659- return true ;
510+ Type annotatedType =
511+ arraySymbol instanceof Symbol .MethodSymbol methodSymbol
512+ ? methodSymbol .getReturnType ()
513+ : arraySymbol .type ;
514+ if (annotatedType instanceof Type .ArrayType arrayType ) {
515+ for (AnnotationMirror annotationMirror :
516+ arrayType .getComponentType ().getAnnotationMirrors ()) {
517+ if (typeUseCheck .test (annotationMirror .getAnnotationType ().toString (), config )) {
518+ return true ;
519+ }
520+ }
660521 }
661522 // For varargs symbols we also check for declaration annotations on the parameter
662523 // NOTE this flag check does not work for the varargs parameter of a method defined in bytecodes
0 commit comments