@NlsSafe
  @NotNull
  private static String describeFunctionWithTypes(@NotNull PyFunction function,
                                                  @NotNull TypeEvalContext context,
                                                  boolean forTooltip) {
    final StringBuilder result = new StringBuilder();
    int firstParamOffset = 0;
    // TODO wrapping of long signatures

    if (function.isAsync()) {
      result.append(styledSpan("async ", PyHighlighter.PY_KEYWORD)); //NON-NLS
      firstParamOffset += "async ".length();
    }
    result.append(styledSpan("def ", PyHighlighter.PY_KEYWORD)); //NON-NLS
    firstParamOffset += "def ".length();

    final String funcName = StringUtil.notNullize(function.getName(), PyNames.UNNAMED_ELEMENT);

    firstParamOffset += funcName.length();
    result.append(styledSpan(funcName, functionNameTextAttribute(function, funcName)));

    result.append(styledSpan("(", PyHighlighter.PY_PARENTHS));
    firstParamOffset++;

    int lastLineOffset = 0;
    boolean first = true;
    boolean firstIsSelf = false;
    final List<PyCallableParameter> parameters = function.getParameters(context);
    for (PyCallableParameter parameter : parameters) {
      boolean isSelf = parameter.isSelf();
      if (!first) {
        result.append(styledSpan(",", PyHighlighter.PY_COMMA));
        if (forTooltip || firstIsSelf && parameters.size() == 2) {
          result.append(" ");
        }
        else {
          result.append("\n");
          lastLineOffset = result.length();
          // alignment
          StringUtil.repeatSymbol(result, ' ', firstParamOffset);
        }
      }
      else {
        firstIsSelf = isSelf;
      }

      String paramName = parameter.getName();
      PyType paramType = parameter.getType(context);
      final PyNamedParameter named = as(parameter.getParameter(), PyNamedParameter.class);
      boolean showType = true;
      if (parameter.isPositionalContainer()) {
        paramName = "*" + StringUtil.notNullize(paramName, "args"); //NON-NLS
        final PyTupleType tupleType = as(paramType, PyTupleType.class);
        if (tupleType != null) {
          paramType = tupleType.getIteratedItemType();
        }
      }
      else if (parameter.isKeywordContainer()) {
        paramName = "**" + StringUtil.notNullize(paramName, "kwargs"); //NON-NLS
        final PyCollectionType genericType = as(paramType, PyCollectionType.class);
        if (genericType != null && genericType.getPyClass() == PyBuiltinCache.getInstance(function).getClass("dict")) {
          final List<PyType> typeParams = genericType.getElementTypes();
          paramType = typeParams.size() == 2 ? typeParams.get(1) : null;
        }
      }
      else if (parameter.getParameter() instanceof PySlashParameter) {
        paramName = PySlashParameter.TEXT;
        showType = false;
      }
      else if (parameter.getParameter() instanceof PySingleStarParameter) {
        paramName = PySingleStarParameter.TEXT;
        showType = false;
      }
      else {
        paramName = StringUtil.notNullize(paramName, PyNames.UNNAMED_ELEMENT);
        // Don't show type for "self" unless it's explicitly annotated
        showType = !isSelf || (named != null && new PyTypingTypeProvider().getParameterType(named, function, context) != null);
      }

      result.append(styledSpan(paramName, paramNameTextAttribute(isSelf)));
      if (showType) {
        result
          .append(styledSpan(": ", PyHighlighter.PY_OPERATION_SIGN))
          .append(styledSpan(formatTypeWithLinks(paramType, named, function, context), PyHighlighter.PY_ANNOTATION));
      }

      final String signature = ParamHelper.getDefaultValuePartInSignature(parameter.getDefaultValueText(), showType);
      if (signature != null) {
        @SuppressWarnings("RegExpRepeatedSpace") final String delimiter = showType ? " = " : "=";
        final String[] parts = signature.split(delimiter);
        if (parts.length == 2) {
          result.append(styledSpan(delimiter, PyHighlighter.PY_OPERATION_SIGN));
          result.append(highlightExpressionText(parts[1], parameter.getDefaultValue()));
        }
      }
      first = false;
    }

    result.append(styledSpan(")", PyHighlighter.PY_PARENTHS));

    if (!forTooltip && StringUtil.stripHtml(result.substring(lastLineOffset), false).length() > RETURN_TYPE_WRAPPING_THRESHOLD) {
      result.append("\n ");
    }
    final PyType returnType = context.getReturnType(function);
    result.append(HtmlChunk.text(" -> "));
    result.append(styledSpan(formatTypeWithLinks(returnType, function, function, context), PyHighlighter.PY_ANNOTATION));
    return result.toString();
  }
@NlsSafe
  @NotNull
  private static String describeFunctionWithTypes(@NotNull PyFunction function,
                                                  @NotNull TypeEvalContext context,
                                                  boolean forTooltip)