Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 18 additions & 4 deletions src/FsAutoComplete.Core/DocumentationFormatter.fs
Original file line number Diff line number Diff line change
Expand Up @@ -641,11 +641,25 @@ module DocumentationFormatter =
|> String.concat ($"{nl} | "))

let delegateTip () =
let invoker =
fse.MembersFunctionsAndValues |> Seq.find (fun f -> f.DisplayName = "Invoke")
let delegateSig = fse.FSharpDelegateSignature

let invokerSig = getFuncSignatureWithIdent displayContext invoker 6
$" ={nl} delegate of{nl}{invokerSig}"
let args =
if delegateSig.DelegateArguments.Count = 0 then
"unit"
else
delegateSig.DelegateArguments
|> Seq.map (fun (name, ty) ->
let typeName = ty |> format displayContext |> fst

match name with
| Some n when not (String.IsNullOrWhiteSpace n) ->
let n = FSharpKeywords.NormalizeIdentifierBackticks n
$"{n}: {typeName}"
| _ -> typeName)
|> String.concat " * "

let retType = delegateSig.DelegateReturnType |> format displayContext |> fst
$" ={nl} delegate of {args} -> {retType}"

let typeTip () =
let constructors =
Expand Down
22 changes: 18 additions & 4 deletions src/FsAutoComplete.Core/SignatureFormatter.fs
Original file line number Diff line number Diff line change
Expand Up @@ -707,11 +707,25 @@ module SignatureFormatter =
|> String.concat $"{nl} | ")

let delegateTip () =
let invoker =
fse.MembersFunctionsAndValues |> Seq.find (fun f -> f.DisplayName = "Invoke")
let delegateSig = fse.FSharpDelegateSignature

let invokerSig = getFuncSignatureWithIdent displayContext invoker 6
$" ={nl} delegate of{nl}{invokerSig}"
let args =
if delegateSig.DelegateArguments.Count = 0 then
"unit"
else
delegateSig.DelegateArguments
|> Seq.map (fun (name, ty) ->
let typeName = formatFSharpType displayContext ty

match name with
| Some n when not (String.IsNullOrWhiteSpace n) ->
let n = FSharpKeywords.NormalizeIdentifierBackticks n
$"{n}: {typeName}"
| _ -> typeName)
|> String.concat " * "

let retType = formatFSharpType displayContext delegateSig.DelegateReturnType
$" ={nl} delegate of {args} -> {retType}"

let typeTip () =
let constructors =
Expand Down
17 changes: 14 additions & 3 deletions test/FsAutoComplete.Tests.Lsp/CoreTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -610,10 +610,21 @@ let tooltipTests state =
// Hover must show `string | null`, not just `string`.
verifySignature 113u 4u "val nullableStringVal: string | null"

verifySignature 114u 4u (concatLines [ "val returnsNullable:"; " s: string"; " -> string | null" ])

// Delegate tooltip format β€” regression for issue #627.
// Verify that hovering on a delegate type definition shows a clean
// "delegate of <args> -> <retType>" signature rather than broken output.
verifySignature 117u 5u (concatLines [ "type NoArgDelegate ="; " delegate of unit -> unit" ])

verifySignature 118u 5u (concatLines [ "type SingleArgDelegate ="; " delegate of arg: string -> int" ])

verifySignature
114u
4u
(concatLines [ "val returnsNullable:"; " s: string"; " -> string | null" ]) ] ]
119u
5u
(concatLines [ "type MultiArgDelegate ="; " delegate of a: string * b: int -> bool" ])

verifySignature 120u 5u (concatLines [ "type UnannotatedDelegate ="; " delegate of string -> int" ]) ] ]

let closeTests state =
// Note: clear diagnostics also implies clear caches (-> remove file & project options from State).
Expand Down
20 changes: 20 additions & 0 deletions test/FsAutoComplete.Tests.Lsp/InfoPanelTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,24 @@ let docFormattingTest state =
| Result.Ok(Some(As(model: FsAutoComplete.CommandResponse.DocumentationDescription))) ->
Expect.stringContains model.Signature "'T1 * 'T2 * 'T3" "Formatted doc contains 3 params separated by ( * )"
| Result.Ok _ -> failtest "couldn't parse doc as the json type we expected"
})

testCaseAsync
"Delegate type shows clean 'delegate of <args> -> <ret>' signature (issue #627)"
(async {
let! (server, path) = server

// Line 3 (0-indexed), char 5: hovering on "DelegateWithArgs" type name
let! doc =
server.FSharpDocumentation
{ TextDocument = { Uri = path }
Position = { Character = 5u; Line = 3u } }

match doc with
| Result.Error err -> failtest $"Doc error: {err.Message}"
| Result.Ok(Some(As(model: FsAutoComplete.CommandResponse.DocumentationDescription))) ->
Expect.stringContains model.Signature "delegate of" "Delegate signature should contain 'delegate of'"
Expect.stringContains model.Signature "name" "Delegate signature should contain parameter name 'name'"
Expect.stringContains model.Signature "count" "Delegate signature should contain parameter name 'count'"
| Result.Ok _ -> failtest "couldn't parse doc as the json type we expected"
}) ]
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
Map.map
List.unzip3
List.unzip3
// Delegate type for issue #627 tooltip tests
type DelegateWithArgs = delegate of name: string * count: int -> bool
6 changes: 6 additions & 0 deletions test/FsAutoComplete.Tests.Lsp/TestCases/Tooltips/Script.fsx
Original file line number Diff line number Diff line change
Expand Up @@ -113,3 +113,9 @@ let functionAliasValue: FunctionAlias = fun _ -> 2
// Nullable reference types (F# 9 | null syntax) β€” issue #1352
let nullableStringVal: string | null = null
let returnsNullable (s: string) : string | null = null

// Delegate types β€” hover tooltip regression for issue #627
type NoArgDelegate = delegate of unit -> unit
type SingleArgDelegate = delegate of arg: string -> int
type MultiArgDelegate = delegate of a: string * b: int -> bool
type UnannotatedDelegate = delegate of string -> int
Loading