private static boolean addConstraints(@NotNull PsiClass recordClass,
                                        @NotNull PsiType type,
                                        PsiElementFactory factory,
                                        InferenceSession session) {
    if (type instanceof PsiClassType) {
      PsiClassType classType = (PsiClassType)type;
      PsiClassType.ClassResolveResult result = classType.resolveGenerics();
      PsiClass gClass = result.getElement();
      if (gClass == null) return false;
      List<PsiTypeParameter> wildcardTypeParams = new ArrayList<>();
      List<PsiWildcardType> wildcardTypes = new ArrayList<>();
      PsiType[] arguments = classType.getParameters();
      PsiTypeParameter[] parameters = gClass.getTypeParameters();
      if (arguments.length == 0 && parameters.length != 0) {
        //18.5.5
        //An initial bound set, B0, is generated from the declared bounds of P1, ..., Pn, as described in §18.1.3.
        //remark: usually, it is covered by `arguments` (see later), but in this case, a set of arguments is empty
        PsiManager manager = recordClass.getManager();
        for (PsiTypeParameter parameter : parameters) {
          PsiReferenceList extendsList = parameter.getExtendsList();
          for (PsiClassType referencedType : extendsList.getReferencedTypes()) {
            wildcardTypeParams.add(parameter);
            wildcardTypes.add(PsiWildcardType.createExtends(manager, referencedType));
          }
        }
      }
      for (int i = 0; i < arguments.length; i++) {
        PsiType argument = arguments[i];
        if (argument instanceof PsiWildcardType && i < parameters.length) {
          wildcardTypeParams.add(parameters[i]);
          wildcardTypes.add((PsiWildcardType)argument);
        }
      }
      if (!wildcardTypeParams.isEmpty()) {
        InferenceVariable[] variables =
          session.initOrReuseVariables(session.getContext(), wildcardTypeParams.toArray(PsiTypeParameter.EMPTY_ARRAY));
        PsiSubstitutor newSubstitutor = result.getSubstitutor();
        for (int i = 0; i < variables.length; i++) {
          PsiWildcardType wildcardType = wildcardTypes.get(i);
          if (wildcardType.isExtends()) {
            variables[i].addBound(wildcardType.getExtendsBound(), InferenceBound.UPPER, null);
          }
          else if (wildcardType.isSuper()) {
            variables[i].addBound(wildcardType.getExtendsBound(), InferenceBound.LOWER, null);
          }
          newSubstitutor = newSubstitutor.put(wildcardTypeParams.get(i), factory.createType(variables[i]));
        }
        type = factory.createType(gClass, newSubstitutor);
      }
      if (gClass instanceof PsiTypeParameter) {
        for (PsiClassType upperBound : gClass.getExtendsListTypes()) {
          if (!addConstraints(recordClass, upperBound, factory, session)) return false;
        }
      }
      else if (InheritanceUtil.isInheritorOrSelf(recordClass, gClass, true)) {
        PsiClassType rAType = factory.createType(recordClass, session.getInferenceSubstitution());
        PsiSubstitutor superClassSubstitutor = TypeConversionUtil.getSuperClassSubstitutor(gClass, rAType);
        PsiType recordPrimeType = factory.createType(gClass, superClassSubstitutor);
        session.addConstraint(new TypeEqualityConstraint(type, recordPrimeType));
      }
    }
    if (type instanceof PsiIntersectionType) {
      for (PsiType conjunct : ((PsiIntersectionType)type).getConjuncts()) {
        if (!addConstraints(recordClass, conjunct, factory, session)) return false;
      }
    }
    return true;
  }

private static boolean addConstraints(@NotNull PsiClass recordClass,
                                        @NotNull PsiType type,
                                        PsiElementFactory factory,
                                        InferenceSession session)