3434import com .google .common .reflect .Reflection ;
3535import com .google .common .reflect .TypeToken ;
3636import java .lang .annotation .Annotation ;
37+ import java .lang .reflect .AnnotatedType ;
3738import java .lang .reflect .Constructor ;
3839import java .lang .reflect .InvocationTargetException ;
3940import java .lang .reflect .Member ;
4041import java .lang .reflect .Method ;
4142import java .lang .reflect .Modifier ;
4243import java .lang .reflect .ParameterizedType ;
4344import java .lang .reflect .Type ;
45+ import java .lang .reflect .TypeVariable ;
4446import java .util .Arrays ;
4547import java .util .List ;
4648import java .util .concurrent .ConcurrentMap ;
49+ import java .util .function .Function ;
4750import junit .framework .Assert ;
4851import junit .framework .AssertionFailedError ;
4952import org .checkerframework .checker .nullness .qual .Nullable ;
5255 * A test utility that verifies that your methods and constructors throw {@link
5356 * NullPointerException} or {@link UnsupportedOperationException} whenever null is passed to a
5457 * parameter whose declaration or type isn't annotated with an annotation with the simple name
55- * {@code Nullable}, {@lcode CheckForNull}, {@link NullableType}, or {@link NullableDecl}.
58+ * {@code Nullable}, {@code CheckForNull}, {@link NullableType}, or {@link NullableDecl}.
5659 *
5760 * <p>The tested methods and constructors are invoked -- each time with one parameter being null and
5861 * the rest not null -- and the test fails if no expected exception is thrown. {@code
@@ -483,7 +486,22 @@ static boolean isNullable(Invokable<?, ?> invokable) {
483486
484487 static boolean isNullable (Parameter param ) {
485488 return isNullable (param .getAnnotatedType ().getAnnotations ())
486- || isNullable (param .getAnnotations ());
489+ || isNullable (param .getAnnotations ())
490+ || isNullableTypeVariable (param .getAnnotatedType ().getType ());
491+ }
492+
493+ private static boolean isNullableTypeVariable (Type type ) {
494+ if (!(type instanceof TypeVariable )) {
495+ return false ;
496+ }
497+ TypeVariable <?> var = (TypeVariable <?>) type ;
498+ AnnotatedType [] bounds = GET_ANNOTATED_BOUNDS .apply (var );
499+ for (AnnotatedType bound : bounds ) {
500+ if (isNullable (bound .getAnnotations ()) || isNullableTypeVariable (bound .getType ())) {
501+ return true ;
502+ }
503+ }
504+ return false ;
487505 }
488506
489507 private static boolean isNullable (Annotation [] annotations ) {
@@ -495,6 +513,27 @@ private static boolean isNullable(Annotation[] annotations) {
495513 return false ;
496514 }
497515
516+ // This is currently required because of j2objc restrictions.
517+ private static final Function <TypeVariable <?>, AnnotatedType []> GET_ANNOTATED_BOUNDS =
518+ initGetAnnotatedBounds ();
519+
520+ private static Function <TypeVariable <?>, AnnotatedType []> initGetAnnotatedBounds () {
521+ AnnotatedType [] noBounds = new AnnotatedType [0 ];
522+ Method getAnnotatedBounds ;
523+ try {
524+ getAnnotatedBounds = TypeVariable .class .getMethod ("getAnnotatedBounds" );
525+ } catch (ReflectiveOperationException e ) {
526+ return v -> noBounds ;
527+ }
528+ return v -> {
529+ try {
530+ return (AnnotatedType []) getAnnotatedBounds .invoke (v );
531+ } catch (ReflectiveOperationException e ) {
532+ return noBounds ;
533+ }
534+ };
535+ }
536+
498537 private boolean isIgnored (Member member ) {
499538 return member .isSynthetic () || ignoredMembers .contains (member ) || isEquals (member );
500539 }
0 commit comments