diff --git a/CHANGELOG.md b/CHANGELOG.md index dbb4ffbdad..21ba925666 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/src/Fantomas.Core.Tests/SignatureTests.fs b/src/Fantomas.Core.Tests/SignatureTests.fs index cbb4fe272d..0ab0b8933b 100644 --- a/src/Fantomas.Core.Tests/SignatureTests.fs +++ b/src/Fantomas.Core.Tests/SignatureTests.fs @@ -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) """ [] @@ -1909,6 +1910,128 @@ namespace Microsoft.FSharp.Control [] [] 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 +""" + +[] +let ``multi-constrained SRTP functions, 2230`` () = + formatSourceString + true + """ +/// Throws ArgumentException +/// +[] +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 ArgumentException +/// +[] +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) +""" + +[] +let ``long curried value with constraints`` () = + formatSourceString + true + """ +[] +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 + """ +[] +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) +""" + +[] +let ``long tupled value with constraints`` () = + formatSourceString + true + """ +[] +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 + """ +[] +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) +""" + +[] +let ``long mixed curried and tuple value with constraints`` () = + formatSourceString + true + """ +[] +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 + """ +[] +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) """ diff --git a/src/Fantomas.Core/CodePrinter.fs b/src/Fantomas.Core/CodePrinter.fs index 8b35fed47c..22b8a96471 100644 --- a/src/Fantomas.Core/CodePrinter.fs +++ b/src/Fantomas.Core/CodePrinter.fs @@ -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 @@ -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 diff --git a/src/Fantomas.Core/Context.fs b/src/Fantomas.Core/Context.fs index 18c3190fe0..99b9d4dcd2 100644 --- a/src/Fantomas.Core/Context.fs +++ b/src/Fantomas.Core/Context.fs @@ -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