Skip to content

Commit

Permalink
Put long multiline constrained SRTP on separate lines. (#2263)
Browse files Browse the repository at this point in the history
* Put long multiline constrained SRTP on separate lines. Fixes #2230.

* Reuse genTypeList in genConstraints.
  • Loading branch information
nojaf committed May 18, 2022
1 parent 24741a3 commit ce92e66
Show file tree
Hide file tree
Showing 4 changed files with 148 additions and 42 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Expand Up @@ -6,6 +6,9 @@
* Idempotency problem with ParsedHashDirective in signature file. [#2258](https://github.com/fsprojects/fantomas/issues/2258)
* Keyword 'override' gets changed to 'member'. [#2221](https://github.com/fsprojects/fantomas/issues/2221)

### Changed
* Formatting of multi-constrained SRTP functions needs improvement. [#2230](https://github.com/fsprojects/fantomas/issues/2230)

## [5.0.0-alpha-007] - 2022-05-16

### Changed
Expand Down
131 changes: 127 additions & 4 deletions src/Fantomas.Core.Tests/SignatureTests.fs
Expand Up @@ -735,8 +735,9 @@ module Foo =
namespace Blah
module Foo =
val inline sum: ('a -> ^value) -> 'a Foo -> ^value
when ^value: (static member (+): ^value * ^value -> ^value) and ^value: (static member Zero: ^value)
val inline sum:
('a -> ^value) -> 'a Foo -> ^value
when ^value: (static member (+): ^value * ^value -> ^value) and ^value: (static member Zero: ^value)
"""

[<Test>]
Expand Down Expand Up @@ -1909,6 +1910,128 @@ namespace Microsoft.FSharp.Control
[<Sealed>]
[<CompiledName("FSharpAsync")>]
type Async =
static member AwaitEvent: event: IEvent<'Del, 'T> * ?cancelAction: (unit -> unit) -> Async<'T>
when 'Del: delegate<'T, unit> and 'Del :> System.Delegate
static member AwaitEvent:
event: IEvent<'Del, 'T> * ?cancelAction: (unit -> unit) -> Async<'T>
when 'Del: delegate<'T, unit> and 'Del :> System.Delegate
"""

[<Test>]
let ``multi-constrained SRTP functions, 2230`` () =
formatSourceString
true
"""
/// Throws <c>ArgumentException</c>
/// </example>
[<CompiledName("Average")>]
val inline average : array:^T[] -> ^T
when ^T : (static member ( + ) : ^T * ^T -> ^T)
and ^T : (static member DivideByInt : ^T*int -> ^T)
and ^T : (static member Zero : ^T)
"""
config
|> prepend newline
|> should
equal
"""
/// Throws <c>ArgumentException</c>
/// </example>
[<CompiledName("Average")>]
val inline average:
array: ^T[] -> ^T
when ^T: (static member (+): ^T * ^T -> ^T)
and ^T: (static member DivideByInt: ^T * int -> ^T)
and ^T: (static member Zero: ^T)
"""

[<Test>]
let ``long curried value with constraints`` () =
formatSourceString
true
"""
[<CompiledName("Average")>]
val inline average: array: ^T[] -> array: ^T[] -> array: ^T[] -> array: ^T[] -> array: ^T[] -> array: ^T[] -> array: ^T[] -> array: ^T[] -> array: ^T[] -> ^T
when ^T: (static member (+): ^T * ^T -> ^T)
and ^T: (static member DivideByInt: ^T * int -> ^T)
and ^T: (static member Zero: ^T)
"""
config
|> prepend newline
|> should
equal
"""
[<CompiledName("Average")>]
val inline average:
array: ^T[] ->
array: ^T[] ->
array: ^T[] ->
array: ^T[] ->
array: ^T[] ->
array: ^T[] ->
array: ^T[] ->
array: ^T[] ->
array: ^T[] ->
^T
when ^T: (static member (+): ^T * ^T -> ^T)
and ^T: (static member DivideByInt: ^T * int -> ^T)
and ^T: (static member Zero: ^T)
"""

[<Test>]
let ``long tupled value with constraints`` () =
formatSourceString
true
"""
[<CompiledName("Average")>]
val inline average: array: ^T[] * array: ^T[] * array: ^T[] * array: ^T[] * array: ^T[] * array: ^T[] * array: ^T[] * array: ^T[] * array: ^T[] -> ^T
when ^T: (static member (+): ^T * ^T -> ^T)
and ^T: (static member DivideByInt: ^T * int -> ^T)
and ^T: (static member Zero: ^T)
"""
config
|> prepend newline
|> should
equal
"""
[<CompiledName("Average")>]
val inline average:
array: ^T[] *
array: ^T[] *
array: ^T[] *
array: ^T[] *
array: ^T[] *
array: ^T[] *
array: ^T[] *
array: ^T[] *
array: ^T[] ->
^T
when ^T: (static member (+): ^T * ^T -> ^T)
and ^T: (static member DivideByInt: ^T * int -> ^T)
and ^T: (static member Zero: ^T)
"""

[<Test>]
let ``long mixed curried and tuple value with constraints`` () =
formatSourceString
true
"""
[<CompiledName("Average")>]
val inline average: array: ^T[] * array: ^T[] * array: ^T[] -> array: ^T[] * array: ^T[] * array: ^T[] -> array: ^T[] * array: ^T[] * array: ^T[] -> ^T
when ^T: (static member (+): ^T * ^T -> ^T)
and ^T: (static member DivideByInt: ^T * int -> ^T)
and ^T: (static member Zero: ^T)
"""
config
|> prepend newline
|> should
equal
"""
[<CompiledName("Average")>]
val inline average:
array: ^T[] * array: ^T[] * array: ^T[] ->
array: ^T[] * array: ^T[] * array: ^T[] ->
array: ^T[] * array: ^T[] * array: ^T[] ->
^T
when ^T: (static member (+): ^T * ^T -> ^T)
and ^T: (static member DivideByInt: ^T * int -> ^T)
and ^T: (static member Zero: ^T)
"""
55 changes: 17 additions & 38 deletions src/Fantomas.Core/CodePrinter.fs
Expand Up @@ -4092,43 +4092,23 @@ and genConstraints astContext (t: SynType) (vi: SynValInfo) =
match t with
| TWithGlobalConstraints (ti, tcs) ->
let genType =
match ti, vi with
| TFuns ts, SynValInfo (curriedArgInfos, returnType) ->
let namedArgInfos =
[ yield! curriedArgInfos
yield [ returnType ] ]

let args = List.zip namedArgInfos ts

col sepArrow args (fun (argInfo, t) ->
match argInfo, t with
| [], _ -> genType astContext false t
| [ SynArgInfo (_, isOptional, Some ident) ], _ ->
onlyIf isOptional (!- "?")
+> genIdent ident
+> sepColon
+> genType astContext false t
| [ SynArgInfo _ ], _ -> genType astContext false t
| multipleArgInfo, TTuple ts ->
let combined = List.zip multipleArgInfo ts

col sepStar combined (fun (argInfo, (_, t)) ->
let genNamed =
match argInfo with
| SynArgInfo (_, isOptional, Some ident) ->
onlyIf isOptional (!- "?")
+> genIdent ident
+> sepColon
| _ -> sepNone

genNamed +> genType astContext false t)
| _ -> sepNone)
| _ -> genType astContext false ti

genType
+> sepSpaceOrIndentAndNlnIfExpressionExceedsPageWidth (
ifElse (List.isNotEmpty tcs) (!- "when ") sepSpace
+> col wordAnd tcs (genTypeConstraint astContext)
let (FunType namedArgs) = (ti, vi)
genTypeList astContext namedArgs

let genConstraints =
let short =
ifElse (List.isNotEmpty tcs) (!- "when ") sepSpace
+> col wordAnd tcs (genTypeConstraint astContext)

let long =
ifElse (List.isNotEmpty tcs) (!- "when ") sepSpace
+> col (sepNln +> wordAndFixed +> sepSpace) tcs (genTypeConstraint astContext)

expressionFitsOnRestOfLine short long

autoIndentAndNlnIfExpressionExceedsPageWidth (
genType
+> sepSpaceOrIndentAndNlnIfExpressionExceedsPageWidth genConstraints
)
| _ -> sepNone

Expand Down Expand Up @@ -4839,7 +4819,6 @@ and genMemberDefn astContext node =
(match t with
| TWithGlobalConstraints _ -> true
| _ -> false)
autoIndentAndNlnIfExpressionExceedsPageWidth
(genConstraints astContext t vi)

| md -> failwithf "Unexpected member definition: %O" md
Expand Down
1 change: 1 addition & 0 deletions src/Fantomas.Core/Context.fs
Expand Up @@ -650,6 +650,7 @@ let internal rep n (f: Context -> Context) (ctx: Context) =
[ 1..n ] |> List.fold (fun c _ -> f c) ctx

let internal wordAnd = !- " and "
let internal wordAndFixed = !- "and"
let internal wordOr = !- " or "
let internal wordOf = !- " of "
// Separator functions
Expand Down

0 comments on commit ce92e66

Please sign in to comment.