@NotNull
  public static GenericSubstitutions getSubstitutionsWithUnresolvedReturnGenerics(@NotNull Collection<PyCallableParameter> parameters,
                                                                                  @Nullable PyType returnType,
                                                                                  @Nullable GenericSubstitutions substitutions,
                                                                                  @NotNull TypeEvalContext context) {
    GenericSubstitutions existingSubstitutions = substitutions == null ? new GenericSubstitutions() : substitutions;
    Generics typeParamsFromReturnType = collectGenerics(returnType, context);
    // TODO Handle unmatched TypeVarTuples here as well
    if (typeParamsFromReturnType.typeVars.isEmpty() && typeParamsFromReturnType.paramSpecs.isEmpty()) {
      return existingSubstitutions;
    }
    Set<PyType> visited = new HashSet<>();
    Generics typeParamsFromParameterTypes = new Generics();
    for (PyCallableParameter parameter : parameters) {
      collectGenerics(parameter.getArgumentType(context), context, typeParamsFromParameterTypes, visited);
    }

    for (PyTypeVarType returnTypeParam : typeParamsFromReturnType.typeVars) {
      boolean canGetBoundFromArguments = typeParamsFromParameterTypes.typeVars.contains(returnTypeParam) ||
                                         typeParamsFromParameterTypes.typeVars.contains(invert(returnTypeParam));
      boolean isAlreadyBound = existingSubstitutions.typeVars.containsKey(returnTypeParam) ||
                               existingSubstitutions.typeVars.containsKey(invert(returnTypeParam));
      if (canGetBoundFromArguments && !isAlreadyBound) {
        existingSubstitutions.typeVars.put(returnTypeParam, null);
      }
    }
    for (PyParamSpecType paramSpecType : typeParamsFromReturnType.paramSpecs) {
      // ParamSpecs already bound to parameter lists 
      if (paramSpecType.getParameters() != null) {
        continue;
      }
      boolean canGetBoundFromArguments = typeParamsFromParameterTypes.paramSpecs.contains(paramSpecType);
      boolean isAlreadyBound = existingSubstitutions.paramSpecs.containsKey(paramSpecType);
      if (canGetBoundFromArguments && !isAlreadyBound) {
        existingSubstitutions.paramSpecs.put(paramSpecType, new PyParamSpecType(paramSpecType.getName())
          .withParameters(List.of(PyCallableParameterImpl.positionalNonPsi("args", null),
                                  PyCallableParameterImpl.keywordNonPsi("kwargs", null)),
                          context));
      }
    }
    return existingSubstitutions;
  }
@NotNull
  public static GenericSubstitutions getSubstitutionsWithUnresolvedReturnGenerics(@NotNull Collection<PyCallableParameter> parameters,
                                                                                  @Nullable PyType returnType,
                                                                                  @Nullable GenericSubstitutions substitutions,
                                                                                  @NotNull TypeEvalContext context)