code: private static boolean typesEquivalent(ParameterizedType typeToMatch, ParameterizedType type, ResolutionContext ctx) {
          if (!typeToMatch.getRawType().equals(type.getRawType())) {
              return false;
          }
  
          Type[] typesA = typeToMatch.getActualTypeArguments();
          Type[] typesB = type.getActualTypeArguments();
  
          if (typesA.length != typesB.length) {
              return false;
          }
  
          for (int i = 0; i < typesA.length; i++) {
              if (!typesEquivalent(typesA[i], ctx.resolve(typesB[i]), ctx)) {
                  return false;
              }
          }
  
          return true;
      }
docs: Computes whether two ParameterizedTypes are equivalent.
  <p>
  This method will check the raw types and then recursively compare each of the type arguments using {@link #typesEquivalent(Type, Type, ResolutionContext)}
  
  @param typeToMatch the type to match against
  @param type the type to check, type variable resolution will be performed for this type
  @param ctx the resolution context to use to perform type variable resolution
  @return {@code true} if {@code type} is equivalent to {@code typeToMatch} after type resolution, otherwise {@code false}