diff --git a/src/Compiler/Checking/SignatureConformance.fs b/src/Compiler/Checking/SignatureConformance.fs
index 8e10990d11d..cf2d2a0bc13 100644
--- a/src/Compiler/Checking/SignatureConformance.fs
+++ b/src/Compiler/Checking/SignatureConformance.fs
@@ -322,6 +322,7 @@ type Checker(g, amap, denv, remapInfo: SignatureRepackageInfo, checkingSig) =
// Propagate defn location information from implementation to signature .
sigVal.SetOtherRange (implVal.Range, true)
implVal.SetOtherRange (sigVal.Range, false)
+ implVal.SetOtherXmlDoc(sigVal.XmlDoc)
let mk_err denv f = ValueNotContained(denv, infoReader, implModRef, implVal, sigVal, f)
let err denv f = errorR(mk_err denv f); false
diff --git a/src/Compiler/TypedTree/TypedTree.fs b/src/Compiler/TypedTree/TypedTree.fs
index 0adaac1f63e..96223d88e8d 100644
--- a/src/Compiler/TypedTree/TypedTree.fs
+++ b/src/Compiler/TypedTree/TypedTree.fs
@@ -2545,7 +2545,10 @@ type ValOptionalData =
/// XML documentation attached to a value.
/// MUTABILITY: for unpickle linkage
- mutable val_xmldoc: XmlDoc
+ mutable val_xmldoc: XmlDoc
+
+ /// the signature xml doc for an item in an implementation file.
+ mutable val_other_xmldoc : XmlDoc option
/// Is the value actually an instance method/property/event that augments
/// a type, and if so what name does it take in the IL?
@@ -2601,6 +2604,7 @@ type Val =
val_repr_info_for_display = None
val_access = TAccess []
val_xmldoc = XmlDoc.Empty
+ val_other_xmldoc = None
val_member_info = None
val_declaring_entity = ParentNone
val_xmldocsig = String.Empty
@@ -2835,7 +2839,13 @@ type Val =
/// Get the declared documentation for the value
member x.XmlDoc =
match x.val_opt_data with
- | Some optData -> optData.val_xmldoc
+ | Some optData ->
+ if not optData.val_xmldoc.IsEmpty then
+ optData.val_xmldoc
+ else
+ match optData.val_other_xmldoc with
+ | Some xmlDoc -> xmlDoc
+ | None -> XmlDoc.Empty
| _ -> XmlDoc.Empty
///Get the signature for the value's XML documentation
@@ -3065,6 +3075,11 @@ type Val =
| Some optData -> optData.val_other_range <- Some m
| _ -> x.val_opt_data <- Some { Val.NewEmptyValOptData() with val_other_range = Some m }
+ member x.SetOtherXmlDoc xmlDoc =
+ match x.val_opt_data with
+ | Some optData -> optData.val_other_xmldoc <- Some xmlDoc
+ | _ -> x.val_opt_data <- Some { Val.NewEmptyValOptData() with val_other_xmldoc = Some xmlDoc }
+
member x.SetDeclaringEntity parent =
match x.val_opt_data with
| Some optData -> optData.val_declaring_entity <- parent
@@ -3119,6 +3134,7 @@ type Val =
val_repr_info = tg.val_repr_info
val_access = tg.val_access
val_xmldoc = tg.val_xmldoc
+ val_other_xmldoc = tg.val_other_xmldoc
val_member_info = tg.val_member_info
val_declaring_entity = tg.val_declaring_entity
val_xmldocsig = tg.val_xmldocsig
diff --git a/src/Compiler/TypedTree/TypedTree.fsi b/src/Compiler/TypedTree/TypedTree.fsi
index 621a9da12bd..c6225097cb3 100644
--- a/src/Compiler/TypedTree/TypedTree.fsi
+++ b/src/Compiler/TypedTree/TypedTree.fsi
@@ -1820,6 +1820,9 @@ type ValOptionalData =
/// MUTABILITY: for unpickle linkage
mutable val_xmldoc: XmlDoc
+ /// the signature xml doc for an item in an implementation file.
+ mutable val_other_xmldoc: XmlDoc option
+
/// Is the value actually an instance method/property/event that augments
/// a type, type if so what name does it take in the IL?
/// MUTABILITY: for unpickle linkage
@@ -1913,6 +1916,8 @@ type Val =
member SetOtherRange: m: (range * bool) -> unit
+ member SetOtherXmlDoc: xmlDoc: XmlDoc -> unit
+
member SetType: ty: TType -> unit
member SetValDefn: val_defn: Expr -> unit
diff --git a/src/Compiler/TypedTree/TypedTreePickle.fs b/src/Compiler/TypedTree/TypedTreePickle.fs
index f19ca1caaa1..13df0443f5c 100644
--- a/src/Compiler/TypedTree/TypedTreePickle.fs
+++ b/src/Compiler/TypedTree/TypedTreePickle.fs
@@ -2262,6 +2262,7 @@ and u_ValData st =
val_const = x14
val_access = x13
val_xmldoc = defaultArg x15 XmlDoc.Empty
+ val_other_xmldoc = None
val_member_info = x8
val_declaring_entity = x13b
val_xmldocsig = x12
diff --git a/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.Tests.fsproj b/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.Tests.fsproj
index ac99cea52ae..0c1d3cf9273 100644
--- a/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.Tests.fsproj
+++ b/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.Tests.fsproj
@@ -186,6 +186,7 @@
PrettyNaming.fs
+
Program.fs
diff --git a/tests/FSharp.Compiler.Service.Tests/TooltipTests.fs b/tests/FSharp.Compiler.Service.Tests/TooltipTests.fs
new file mode 100644
index 00000000000..7ea0e5323c8
--- /dev/null
+++ b/tests/FSharp.Compiler.Service.Tests/TooltipTests.fs
@@ -0,0 +1,68 @@
+module FSharp.Compiler.Service.Tests.TooltipTests
+
+#nowarn "57"
+
+open FSharp.Compiler.CodeAnalysis
+open FSharp.Compiler.Service.Tests.Common
+open FSharp.Compiler.Text
+open FSharp.Compiler.Tokenization
+open FSharp.Compiler.EditorServices
+open FSharp.Compiler.Symbols
+open NUnit.Framework
+
+[]
+let ``Display XML doc of signature file if implementation doesn't have one`` () =
+ let files =
+ Map.ofArray
+ [| "A.fsi",
+ SourceText.ofString
+ """
+module Foo
+
+/// Great XML doc comment
+val bar: a: int -> b: int -> int
+"""
+
+ "A.fs",
+ SourceText.ofString
+ """
+module Foo
+
+// No XML doc here because the signature file has one right?
+let bar a b = a - b
+""" |]
+
+ let documentSource fileName = Map.tryFind fileName files
+
+ let projectOptions =
+ let _, projectOptions = mkTestFileAndOptions "" Array.empty
+
+ { projectOptions with
+ SourceFiles = [| "A.fsi"; "A.fs" |] }
+
+ let checker =
+ FSharpChecker.Create(documentSource = DocumentSource.Custom documentSource)
+
+ let checkResult =
+ checker.ParseAndCheckFileInProject("A.fs", 0, Map.find "A.fs" files, projectOptions)
+ |> Async.RunImmediate
+
+ match checkResult with
+ | _, FSharpCheckFileAnswer.Succeeded(checkResults) ->
+ let barSymbol = findSymbolByName "bar" checkResults
+
+ match barSymbol with
+ | :? FSharpMemberOrFunctionOrValue as mfv -> Assert.True mfv.HasSignatureFile
+ | _ -> Assert.Fail "Expected to find a symbol FSharpMemberOrFunctionOrValue that HasSignatureFile"
+
+ // Get the tooltip for `bar` in the implementation file
+ let (ToolTipText tooltipElements) =
+ checkResults.GetToolTip(4, 4, "let bar a b = a - b", [ "bar" ], FSharpTokenTag.Identifier)
+
+ match tooltipElements with
+ | [ ToolTipElement.Group [ element ] ] ->
+ match element.XmlDoc with
+ | FSharpXmlDoc.FromXmlText xmlDoc -> Assert.True xmlDoc.NonEmpty
+ | xmlDoc -> Assert.Fail $"Expected FSharpXmlDoc.FromXmlText, got {xmlDoc}"
+ | elements -> Assert.Fail $"Expected a single tooltip group element, got {elements}"
+ | _ -> Assert.Fail "Expected checking to succeed."