@NotNull
  private Map<PyType, PyType> calculateGenericSubstitutions(@NotNull PyClass cls, @NotNull Context context) {
    if (!isGeneric(cls, context.myContext)) {
      return Collections.emptyMap();
    }
    Map<PyType, PyType> results = new HashMap<>();
    for (PyClassType superClassType : evaluateSuperClassesAsTypeHints(cls, context.myContext)) {
      Map<PyType, PyType> superSubstitutions =
        doPreventingRecursion(superClassType.getPyClass(), false, () -> getGenericSubstitutions(superClassType.getPyClass(), context));
      if (superSubstitutions != null) {
        results.putAll(superSubstitutions);
      }
      // TODO Share this logic with PyTypeChecker.collectTypeSubstitutions
      List<PyTypeParameterType> superTypeParameters = collectTypeParameters(superClassType.getPyClass(), context);
      List<PyType> superTypeArguments = superClassType instanceof PyCollectionType parameterized ?
                                        parameterized.getElementTypes() : Collections.emptyList();
      PyTypeParameterMapping mapping =
        PyTypeParameterMapping.mapByShape(superTypeParameters, superTypeArguments, Option.MAP_UNMATCHED_EXPECTED_TYPES_TO_ANY);
      if (mapping != null) {
        for (Couple<PyType> pair : mapping.getMappedTypes()) {
          PyType expectedType = pair.getFirst();
          PyType actualType = pair.getSecond();
          if (!expectedType.equals(actualType)) {
            results.put(expectedType, actualType);
          }
        }
      }
    }
    return results;
  }
@NotNull
  private Map<PyType, PyType> calculateGenericSubstitutions(@NotNull PyClass cls, @NotNull Context context)