@NotNull
 private static List<PyGenericType> collectGenericTypes(
     @NotNull PyClass cls, @NotNull Context context) {
   boolean isGeneric = false;
   for (PyClass ancestor : cls.getAncestorClasses(context.getTypeContext())) {
     if (GENERIC_CLASSES.contains(ancestor.getQualifiedName())) {
       isGeneric = true;
       break;
     }
   }
   if (isGeneric) {
     final ArrayList<PyGenericType> results = new ArrayList<>();
     // XXX: Requires switching from stub to AST
     for (PyExpression expr : cls.getSuperClassExpressions()) {
       if (expr instanceof PySubscriptionExpression) {
         final PyExpression indexExpr = ((PySubscriptionExpression) expr).getIndexExpression();
         if (indexExpr != null) {
           for (PsiElement resolved : tryResolving(indexExpr, context.getTypeContext())) {
             final PyGenericType genericType = getGenericType(resolved, context);
             if (genericType != null) {
               results.add(genericType);
             }
           }
         }
       }
     }
     return results;
   }
   return Collections.emptyList();
 }
  private void addInheritedDocString(
      @NotNull final PyFunction pyFunction, @Nullable final PyClass pyClass) {
    boolean notFound = true;
    final String methodName = pyFunction.getName();
    if (pyClass == null || methodName == null) {
      return;
    }
    final boolean isConstructor = PyNames.INIT.equals(methodName);
    Iterable<PyClass> classes = pyClass.getAncestorClasses(null);
    if (isConstructor) {
      // look at our own class again and maybe inherit class's doc
      classes = new ChainIterable<PyClass>(pyClass).add(classes);
    }
    for (PyClass ancestor : classes) {
      PyStringLiteralExpression docstringElement = null;
      PyFunction inherited = null;
      boolean isFromClass = false;
      if (isConstructor) docstringElement = pyClass.getDocStringExpression();
      if (docstringElement != null) {
        isFromClass = true;
      } else {
        inherited = ancestor.findMethodByName(methodName, false);
      }
      if (inherited != null) {
        docstringElement = inherited.getDocStringExpression();
      }
      if (docstringElement != null) {
        final String inheritedDoc = docstringElement.getStringValue();
        if (inheritedDoc.length() > 1) {
          myEpilog.addItem(BR).addItem(BR);
          String ancestor_name = ancestor.getName();
          String marker =
              (pyClass == ancestor)
                  ? PythonDocumentationProvider.LINK_TYPE_CLASS
                  : PythonDocumentationProvider.LINK_TYPE_PARENT;
          final String ancestor_link =
              $().addWith(new LinkWrapper(marker + ancestor_name), $(ancestor_name)).toString();
          if (isFromClass) {
            myEpilog.addItem(PyBundle.message("QDOC.copied.from.class.$0", ancestor_link));
          } else {
            myEpilog.addItem(PyBundle.message("QDOC.copied.from.$0.$1", ancestor_link, methodName));
          }
          myEpilog.addItem(BR).addItem(BR);
          ChainIterable<String> formatted = new ChainIterable<String>();
          ChainIterable<String> unformatted = new ChainIterable<String>();
          addFormattedDocString(pyFunction, inheritedDoc, formatted, unformatted);
          myEpilog.addWith(TagCode, formatted).add(unformatted);
          notFound = false;
          break;
        }
      }
    }

    if (notFound) {
      // above could have not worked because inheritance is not searched down to 'object'.
      // for well-known methods, copy built-in doc string.
      // TODO: also handle predefined __xxx__ that are not part of 'object'.
      if (PyNames.UnderscoredAttributes.contains(methodName)) {
        addPredefinedMethodDoc(pyFunction, methodName);
      }
    }
  }