fsharp / fslang-design

RFCs and docs related to the F# language design process, see https://github.com/fsharp/fslang-suggestions to submit ideas
518 stars 144 forks source link

[style-guide] Multiline type annotations #708

Closed nojaf closed 1 year ago

nojaf commented 2 years ago

The current guidance doesn't explicitly state the best practice regarding multiline type annotations.

Abstractly put:

p: t

Where t is multiline or crossed the maximum line length.

I propose to simply indent t on the following line if these conditions are met.

p:
    t

Some examples to illustrate:

type ExprFolder<'State> =
    { exprIntercept: 
        ('State -> Expr -> 'State) -> ('State -> Expr -> 'State -> 'State -> Exp -> 'State }

let UpdateUI
    (theModel:
#if NETCOREAPP2_1
        ITreeModel
#else
        TreeModel
#endif
    )
    (info: FileInfo) =
    // code
    ()

let f
    (x:
        {|
            x: int
            y: AReallyLongTypeThatIsMuchLongerThan40Characters
        |})
    =
    x

I believe in practice, this won't show up that much. Nevertheless, the principle still holds and is consistent with other parts of the guide. It also does not introduce any vanity alignments.

Some further context can be found in https://github.com/fsprojects/fantomas/pull/2424#issuecomment-1230033101


Somewhat related, if t is placed on the next line, depending on what it is, it might still need alternate formatting to respect the max_line_length.

If t is a long tuple type, I would also split it onto multiline lines. Similar to how signature files are described in the guide.

p:
    a *
    b *
    c

Relevant example:

type Meh
  (
    input: 
        LongTupleItemTypeOneThing * 
        LongTupleItemTypeThingTwo * 
        LongTupleItemTypeThree * 
        LongThingFour * 
        LongThingFiveYow
  ) =
  class
  end

For lambda expression, in the bizarre case that the arguments cannot fit on the same line of the fun keyword, I would indent them on the next line.

fun 
   (a: t1)
   (b: t2) ->
    ()

Relevant example:

let useAddEntry () =
    fun
        (input:
            {| name: string
               amount: Amount
               isIncome: bool
               created: string |}) ->
         // foo
         bar ()

//cc @dsyme @josh-degraw

dsyme commented 1 year ago

Looks right 👍

dsyme commented 1 year ago

@nojaf When you implement this, could you send a link to all the changes in the Fantomas tests for second review? I want to make sure there aren't any unintended consequences.

nojaf commented 1 year ago

Will do! @josh-degraw could I persuade you to pair on these?

josh-degraw commented 1 year ago

Just saw this, yeah I'd be down!

nojaf commented 1 year ago

@josh-degraw I already went ahead with this one. I did not change anything regarding anonymous records, we can pair on that together and finish https://github.com/fsprojects/fantomas/pull/2424.