docs: def build_fncall(
      ctx,
      fndoc,
      argdocs=(),
      kwargdocs=(),
      hug_sole_arg=False,
      trailing_comment=None,
  ):
      """Builds a doc that looks like a function call,
      from docs that represent the function, arguments
      and keyword arguments.
  
      If ``hug_sole_arg`` is True, and the represented
      functional call is done with a single non-keyword
      argument, the function call parentheses will hug
      the sole argument doc without newlines and indentation
      in break mode. This makes a difference in calls
      like this::
  
          > hug_sole_arg = False
          frozenset(
              [
                  1,
                  2,
                  3,
                  4,
                  5
              ]
          )
          > hug_sole_arg = True
          frozenset([
              1,
              2,
              3,
              4,
              5,
          ])
  
      If ``trailing_comment`` is provided, the text is
      rendered as a comment after the last argument and
      before the closing parenthesis. This will force
      the function call to be broken to multiple lines.
      """
      if callable(fndoc):
          fndoc = general_identifier(fndoc)
  
      has_comment = bool(trailing_comment)
  
      argdocs = list(argdocs)
      kwargdocs = list(kwargdocs)
  
      kwargdocs = [
          # Propagate any comments to the kwarg doc.
          (
              comment_doc(
                  concat([
                      keyword_arg(binding),
                      ASSIGN_OP,
                      doc.doc
                  ]),
                  doc.annotation.value
              )
              if is_commented(doc)
              else concat([
                  keyword_arg(binding),
                  ASSIGN_OP,
                  doc
              ])
          )
          for binding, doc in kwargdocs
      ]
  
      if not (argdocs or kwargdocs):
          return concat([
              fndoc,
              LPAREN,
              RPAREN,
          ])
  
      if (
          hug_sole_arg and
          not kwargdocs and
          len(argdocs) == 1 and
          not is_commented(argdocs[0])
      ):
          return group(
              concat([
                  fndoc,
                  LPAREN,
                  argdocs[0],
                  RPAREN
              ])
          )
  
      allarg_docs = [*argdocs, *kwargdocs]
  
      if trailing_comment:
          allarg_docs.append(commentdoc(trailing_comment))
  
      parts = []
  
      for idx, doc in enumerate(allarg_docs):
          last = idx == len(allarg_docs) - 1
  
          if is_commented(doc):
              has_comment = True
              comment_str = doc.annotation.value
              doc = doc.doc
          else:
              comment_str = None
  
          part = concat([doc, NIL if last else COMMA])
  
          if comment_str:
              part = group(
                  flat_choice(
                      when_flat=concat([
                          part,
                          '  ',
                          commentdoc(comment_str)
                      ]),
                      when_broken=concat([
                          commentdoc(comment_str),
                          HARDLINE,
                          part,
                      ]),
                  )
              )
  
          if not last:
              part = concat([part, HARDLINE if has_comment else LINE])
  
          parts.append(part)
  
      outer = (
          always_break
          if has_comment
          else group
      )
  
      return outer(
          concat([
              fndoc,
              LPAREN,
              nest(
                  ctx.indent,
                  concat([
                      SOFTLINE,
                      concat(parts),
                  ])
              ),
              SOFTLINE,
              RPAREN
          ])
      )