// Returns list with type arguments info from supertypes
  // Example:
  //     - Foo<A, B> is a subtype of Bar<A, List<B>>, Baz<Boolean, A>
  //     - input: klass = Foo, typesFromSuper = [Bar<String, List<Int>>, Baz<Boolean, CharSequence>]
  //     - output[0] = [String, CharSequence], output[1] = []
  private static List<List<TypeProjectionAndVariance>> calculateTypeArgumentsFromSuper(
      @NotNull ClassDescriptor klass, @NotNull Collection<TypeAndVariance> typesFromSuper) {
    // For each superclass of klass and its parameters, hold their mapping to klass' parameters
    // #0 of Bar ->  A
    // #1 of Bar ->  List<B>
    // #0 of Baz ->  Boolean
    // #1 of Baz ->  A
    // #0 of Foo ->  A (mapped to itself)
    // #1 of Foo ->  B (mapped to itself)
    Multimap<TypeConstructor, TypeProjection> substitution =
        SubstitutionUtils.buildDeepSubstitutionMultimap(
            TypeUtils.makeUnsubstitutedType(klass, JetScope.EMPTY));

    // for each parameter of klass, hold arguments in corresponding supertypes
    List<List<TypeProjectionAndVariance>> parameterToArgumentsFromSuper = Lists.newArrayList();
    for (TypeParameterDescriptor ignored : klass.getTypeConstructor().getParameters()) {
      parameterToArgumentsFromSuper.add(new ArrayList<TypeProjectionAndVariance>());
    }

    // Enumerate all types from super and all its parameters
    for (TypeAndVariance typeFromSuper : typesFromSuper) {
      for (TypeParameterDescriptor parameter :
          typeFromSuper.type.getConstructor().getParameters()) {
        TypeProjection argument = typeFromSuper.type.getArguments().get(parameter.getIndex());

        // for given example, this block is executed four times:
        // 1. typeFromSuper = Bar<String, List<Int>>,      parameter = "#0 of Bar",  argument =
        // String
        // 2. typeFromSuper = Bar<String, List<Int>>,      parameter = "#1 of Bar",  argument =
        // List<Int>
        // 3. typeFromSuper = Baz<Boolean, CharSequence>,  parameter = "#0 of Baz",  argument =
        // Boolean
        // 4. typeFromSuper = Baz<Boolean, CharSequence>,  parameter = "#1 of Baz",  argument =
        // CharSequence

        // if it is mapped to klass' parameter, then store it into map
        for (TypeProjection projection : substitution.get(parameter.getTypeConstructor())) {
          // 1. projection = A
          // 2. projection = List<B>
          // 3. projection = Boolean
          // 4. projection = A
          ClassifierDescriptor classifier =
              projection.getType().getConstructor().getDeclarationDescriptor();

          // this condition is true for 1 and 4, false for 2 and 3
          if (classifier instanceof TypeParameterDescriptor
              && classifier.getContainingDeclaration() == klass) {
            int parameterIndex = ((TypeParameterDescriptor) classifier).getIndex();
            Variance effectiveVariance =
                parameter.getVariance().superpose(typeFromSuper.varianceOfPosition);
            parameterToArgumentsFromSuper
                .get(parameterIndex)
                .add(new TypeProjectionAndVariance(argument, effectiveVariance));
          }
        }
      }
    }
    return parameterToArgumentsFromSuper;
  }
  private List<TypeParameterDescriptor> modifyTypeParametersAccordingToSuperMethods(
      List<TypeParameterDescriptor> autoTypeParameters) {
    List<TypeParameterDescriptor> result = Lists.newArrayList();

    for (TypeParameterDescriptor autoParameter : autoTypeParameters) {
      int index = autoParameter.getIndex();
      TypeParameterDescriptorImpl modifiedTypeParameter =
          autoTypeParameterToModified.get(autoParameter);

      List<Iterator<JetType>> upperBoundFromSuperFunctionsIterators = Lists.newArrayList();
      for (FunctionDescriptor superFunction : superFunctions) {
        upperBoundFromSuperFunctionsIterators.add(
            superFunction.getTypeParameters().get(index).getUpperBounds().iterator());
      }

      for (JetType autoUpperBound : autoParameter.getUpperBounds()) {
        List<TypeAndVariance> upperBoundsFromSuperFunctions = Lists.newArrayList();

        for (Iterator<JetType> iterator : upperBoundFromSuperFunctionsIterators) {
          assert iterator.hasNext();
          upperBoundsFromSuperFunctions.add(new TypeAndVariance(iterator.next(), INVARIANT));
        }

        JetType modifiedUpperBound =
            modifyTypeAccordingToSuperMethods(
                autoUpperBound, upperBoundsFromSuperFunctions, UPPER_BOUND);
        modifiedTypeParameter.addUpperBound(modifiedUpperBound);
      }

      for (Iterator<JetType> iterator : upperBoundFromSuperFunctionsIterators) {
        assert !iterator.hasNext();
      }

      modifiedTypeParameter.setInitialized();
      result.add(modifiedTypeParameter);
    }

    return result;
  }
  @NotNull
  private List<TypeProjection> getTypeArgsOfType(
      @NotNull JetType autoType,
      @NotNull ClassifierDescriptor classifier,
      @NotNull List<TypeAndVariance> typesFromSuper) {
    List<TypeProjection> autoArguments = autoType.getArguments();

    if (!(classifier instanceof ClassDescriptor)) {
      assert autoArguments.isEmpty()
          : "Unexpected type arguments when type constructor is not ClassDescriptor, type = "
              + autoType;
      return autoArguments;
    }

    List<List<TypeProjectionAndVariance>> typeArgumentsFromSuper =
        calculateTypeArgumentsFromSuper((ClassDescriptor) classifier, typesFromSuper);

    // Modify type arguments using info from typesFromSuper
    List<TypeProjection> resultArguments = Lists.newArrayList();
    for (TypeParameterDescriptor parameter : classifier.getTypeConstructor().getParameters()) {
      TypeProjection argument = autoArguments.get(parameter.getIndex());

      JetType argumentType = argument.getType();
      List<TypeProjectionAndVariance> projectionsFromSuper =
          typeArgumentsFromSuper.get(parameter.getIndex());
      List<TypeAndVariance> argTypesFromSuper = getTypes(projectionsFromSuper);

      JetType type =
          modifyTypeAccordingToSuperMethods(argumentType, argTypesFromSuper, TYPE_ARGUMENT);
      Variance projectionKind =
          calculateArgumentProjectionKindFromSuper(argument, projectionsFromSuper);

      resultArguments.add(new TypeProjection(projectionKind, type));
    }
    return resultArguments;
  }