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."