code: private static boolean typesEquivalent(Type typeToMatch, Type type, ResolutionContext ctx) {
          type = ctx.resolve(type);
          if (typeToMatch instanceof ParameterizedType) {
              if (type instanceof ParameterizedType) {
                  return typesEquivalent((ParameterizedType) typeToMatch, (ParameterizedType) type, ctx);
              } else {
                  return false;
              }
          } else {
              if (type instanceof ParameterizedType) {
                  return false;
              }
          }
  
          if (typeToMatch instanceof WildcardType) {
              if (type instanceof WildcardType) {
                  return typesEquivalent((WildcardType) typeToMatch, (WildcardType) type, ctx);
              } else {
                  return false;
              }
          } else {
              if (type instanceof WildcardType) {
                  return false;
              }
          }
  
          if (type instanceof GenericArrayType) {
              return typesEquivalent(typeToMatch, (GenericArrayType) type, ctx);
          }
  
          return typeToMatch.equals(type);
      }
docs: Computes whether {@code type} is equivalent to {@code typeToMatch}
  <p>
  If {@code type} is a type variable, or is a ParameterizedType which has type variables, or is a GenericArrayType, {@code ctx} will be used to resolve any type variables,
  if possible.
  <p>
  The type variable resolution is not done for {@code typeToMatch}.
  <p>
  This method will delegate to {@link #typesEquivalent(ParameterizedType, ParameterizedType, ResolutionContext)} and
  {@link #typesEquivalent(Type, GenericArrayType, 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}