From 8816587c6e445d03a6034af1c09f5a17478b391b Mon Sep 17 00:00:00 2001 From: Copilot <223556219+Copilot@users.noreply.github.com> Date: Thu, 26 Feb 2026 18:20:02 +0000 Subject: [PATCH 1/4] fix: improve delegate type tooltips using FSharpDelegateSignature API Closes #627 The previous implementation used getFuncSignatureWithIdent on the delegate's Invoke method, which produced badly formatted multi-line output with excess indentation. For no-argument delegates the output was especially broken (just 'unit' with no indentation). Replace with direct use of FSharpEntity.FSharpDelegateSignature, which provides the delegate arguments (with optional names) and return type. This produces a clean single-line format: type MyDelegate = delegate of arg: string -> int instead of: type MyDelegate = delegate of arg: string -> int For zero-argument delegates, 'unit' is shown as the argument type, consistent with F# delegate syntax. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../DocumentationFormatter.fs | 22 +++++++++++++++---- src/FsAutoComplete.Core/SignatureFormatter.fs | 22 +++++++++++++++---- 2 files changed, 36 insertions(+), 8 deletions(-) diff --git a/src/FsAutoComplete.Core/DocumentationFormatter.fs b/src/FsAutoComplete.Core/DocumentationFormatter.fs index e0f7b1be9..1090e28a0 100644 --- a/src/FsAutoComplete.Core/DocumentationFormatter.fs +++ b/src/FsAutoComplete.Core/DocumentationFormatter.fs @@ -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 = diff --git a/src/FsAutoComplete.Core/SignatureFormatter.fs b/src/FsAutoComplete.Core/SignatureFormatter.fs index 05fead602..b3c5fdae8 100644 --- a/src/FsAutoComplete.Core/SignatureFormatter.fs +++ b/src/FsAutoComplete.Core/SignatureFormatter.fs @@ -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 = From b5cb1620b522b8a2c2b61b9d859ebcb1f22a25ea Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 26 Feb 2026 18:21:35 +0000 Subject: [PATCH 2/4] ci: trigger CI checks From 718b701e0540f04487d2b780ee05eeb4b22d1cf4 Mon Sep 17 00:00:00 2001 From: Repo Assist Date: Thu, 26 Feb 2026 21:47:43 +0000 Subject: [PATCH 3/4] tests: add delegate tooltip hover tests for issue #627 Add e2e hover tests (verifySignature) that verify the delegate type signature formatter produces clean output: - NoArgDelegate (unit -> unit) - SingleArgDelegate (named arg -> int) - MultiArgDelegate (two named args -> bool) - UnannotatedDelegate (unnamed arg -> int) Also add a DocumentationFormatter test via the FSharpDocumentation endpoint (InfoPanelTests) to cover the parallel change in DocumentationFormatter.fs. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- test/FsAutoComplete.Tests.Lsp/CoreTests.fs | 17 +++++++++++++--- .../InfoPanelTests.fs | 20 +++++++++++++++++++ .../FormattedDocumentation/Script.fsx | 4 +++- .../TestCases/Tooltips/Script.fsx | 6 ++++++ 4 files changed, 43 insertions(+), 4 deletions(-) diff --git a/test/FsAutoComplete.Tests.Lsp/CoreTests.fs b/test/FsAutoComplete.Tests.Lsp/CoreTests.fs index ef164a40d..fd3581da7 100644 --- a/test/FsAutoComplete.Tests.Lsp/CoreTests.fs +++ b/test/FsAutoComplete.Tests.Lsp/CoreTests.fs @@ -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 -> " 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). diff --git a/test/FsAutoComplete.Tests.Lsp/InfoPanelTests.fs b/test/FsAutoComplete.Tests.Lsp/InfoPanelTests.fs index 794efb1a8..e7040aa76 100644 --- a/test/FsAutoComplete.Tests.Lsp/InfoPanelTests.fs +++ b/test/FsAutoComplete.Tests.Lsp/InfoPanelTests.fs @@ -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 -> ' 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" }) ] diff --git a/test/FsAutoComplete.Tests.Lsp/TestCases/FormattedDocumentation/Script.fsx b/test/FsAutoComplete.Tests.Lsp/TestCases/FormattedDocumentation/Script.fsx index 78b0856f7..4f827f848 100644 --- a/test/FsAutoComplete.Tests.Lsp/TestCases/FormattedDocumentation/Script.fsx +++ b/test/FsAutoComplete.Tests.Lsp/TestCases/FormattedDocumentation/Script.fsx @@ -1,2 +1,4 @@ Map.map -List.unzip3 \ No newline at end of file +List.unzip3 +// Delegate type for issue #627 tooltip tests +type DelegateWithArgs = delegate of name: string * count: int -> bool \ No newline at end of file diff --git a/test/FsAutoComplete.Tests.Lsp/TestCases/Tooltips/Script.fsx b/test/FsAutoComplete.Tests.Lsp/TestCases/Tooltips/Script.fsx index 5506189ab..adb0bf384 100644 --- a/test/FsAutoComplete.Tests.Lsp/TestCases/Tooltips/Script.fsx +++ b/test/FsAutoComplete.Tests.Lsp/TestCases/Tooltips/Script.fsx @@ -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 From cde1a74ac3cac21aadb6ef6482c48304970b9831 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 26 Feb 2026 21:49:25 +0000 Subject: [PATCH 4/4] ci: trigger CI checks