@NotNull
  public static GenericSubstitutions unifyReceiver(@Nullable PyExpression receiver, @NotNull TypeEvalContext context) {
    // Collect generic params of object type
    final var substitutions = new GenericSubstitutions();
    if (receiver != null) {
      // TODO properly handle union types here
      PyType receiverType = context.getType(receiver);
      if (receiverType instanceof PyClassType) {
        substitutions.qualifierType = ((PyClassType)receiverType).toInstance();
      }
      else {
        substitutions.qualifierType = receiverType;
      }
      PyTypeUtil.toStream(receiverType)
        .select(PyClassType.class)
        .map(type -> collectTypeSubstitutions(type, context))
        .forEach(newSubstitutions -> {
          for (Map.Entry<PyTypeVarType, PyType> typeVarMapping : newSubstitutions.typeVars.entrySet()) {
            substitutions.typeVars.putIfAbsent(typeVarMapping.getKey(), typeVarMapping.getValue());
          }
          for (Map.Entry<PyTypeVarTupleType, PyVariadicType> typeVarMapping : newSubstitutions.typeVarTuples.entrySet()) {
            substitutions.typeVarTuples.putIfAbsent(typeVarMapping.getKey(), typeVarMapping.getValue());
          }
          for (Map.Entry<PyParamSpecType, PyParamSpecType> paramSpecMapping : newSubstitutions.paramSpecs.entrySet()) {
            substitutions.paramSpecs.putIfAbsent(paramSpecMapping.getKey(), paramSpecMapping.getValue());
          }
        });
    }
    return substitutions;
  }
@NotNull
  public static GenericSubstitutions unifyReceiver(@Nullable PyExpression receiver, @NotNull TypeEvalContext context)