From ba6629f70fbfb5e66d357db59cc5e04da0b89940 Mon Sep 17 00:00:00 2001
From: Petr <psfinaki@users.noreply.github.com>
Date: Mon, 17 Oct 2022 17:24:14 +0200
Subject: [PATCH 1/4] Inline type hints for F#

---
 .../src/FSharp.Editor/FSharp.Editor.fsproj    |   3 +
 .../src/FSharp.Editor/Hints/HintService.fs    |  78 +++++++++++++
 .../Hints/NativeToRoslynHintConverter.fs      |  27 +++++
 .../src/FSharp.Editor/Hints/RoslynAdapter.fs  |  38 +++++++
 .../FSharp.Editor/Options/EditorOptions.fs    |   6 +-
 .../AdvancedOptionsControl.xaml               |   4 +
 .../FSharp.UIResources/Strings.Designer.cs    |  18 +++
 .../src/FSharp.UIResources/Strings.resx       |   6 +
 .../src/FSharp.UIResources/xlf/Strings.cs.xlf |  10 ++
 .../src/FSharp.UIResources/xlf/Strings.de.xlf |  10 ++
 .../src/FSharp.UIResources/xlf/Strings.es.xlf |  10 ++
 .../src/FSharp.UIResources/xlf/Strings.fr.xlf |  10 ++
 .../src/FSharp.UIResources/xlf/Strings.it.xlf |  10 ++
 .../src/FSharp.UIResources/xlf/Strings.ja.xlf |  10 ++
 .../src/FSharp.UIResources/xlf/Strings.ko.xlf |  10 ++
 .../src/FSharp.UIResources/xlf/Strings.pl.xlf |  10 ++
 .../FSharp.UIResources/xlf/Strings.pt-BR.xlf  |  10 ++
 .../src/FSharp.UIResources/xlf/Strings.ru.xlf |  10 ++
 .../src/FSharp.UIResources/xlf/Strings.tr.xlf |  10 ++
 .../xlf/Strings.zh-Hans.xlf                   |  10 ++
 .../xlf/Strings.zh-Hant.xlf                   |  10 ++
 .../tests/UnitTests/HintServiceTests.fs       | 104 ++++++++++++++++++
 .../tests/UnitTests/HintTestFramework.fs      |  53 +++++++++
 .../tests/UnitTests/Tests.RoslynHelpers.fs    |  52 +++++++++
 .../UnitTests/VisualFSharp.UnitTests.fsproj   |   6 +
 25 files changed, 523 insertions(+), 2 deletions(-)
 create mode 100644 vsintegration/src/FSharp.Editor/Hints/HintService.fs
 create mode 100644 vsintegration/src/FSharp.Editor/Hints/NativeToRoslynHintConverter.fs
 create mode 100644 vsintegration/src/FSharp.Editor/Hints/RoslynAdapter.fs
 create mode 100644 vsintegration/tests/UnitTests/HintServiceTests.fs
 create mode 100644 vsintegration/tests/UnitTests/HintTestFramework.fs

diff --git a/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj b/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj
index cf1ff4e9c25..cdfc599466f 100644
--- a/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj
+++ b/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj
@@ -126,6 +126,9 @@
     <Compile Include="Build\SetGlobalPropertiesForSdkProjects.fs" />
     <Compile Include="AutomaticCompletion\BraceCompletionSessionProvider.fsi" />
     <Compile Include="AutomaticCompletion\BraceCompletionSessionProvider.fs" />
+    <Compile Include="Hints\HintService.fs" />
+    <Compile Include="Hints\NativeToRoslynHintConverter.fs" />
+    <Compile Include="Hints\RoslynAdapter.fs" />
     <Compile Include="Lens\LensDisplayService.fs" />
     <Compile Include="Lens\LensService.fs" />
     <Compile Include="Lens\LensProvider.fs" />
diff --git a/vsintegration/src/FSharp.Editor/Hints/HintService.fs b/vsintegration/src/FSharp.Editor/Hints/HintService.fs
new file mode 100644
index 00000000000..aaa2f23b5ff
--- /dev/null
+++ b/vsintegration/src/FSharp.Editor/Hints/HintService.fs
@@ -0,0 +1,78 @@
+﻿// Copyright (c) Microsoft Corporation.  All Rights Reserved.  See License.txt in the project root for license information.
+
+namespace Microsoft.VisualStudio.FSharp.Editor.Hints
+
+open Microsoft.CodeAnalysis
+open Microsoft.VisualStudio.FSharp.Editor
+open FSharp.Compiler.CodeAnalysis
+open FSharp.Compiler.Symbols
+open FSharp.Compiler.Text
+
+module HintService =
+
+    // Relatively convenient for testing
+    type NativeHint = {
+        Range: range
+        Parts: seq<TaggedText>
+    }
+
+    let private isValidForHint 
+        (parseFileResults: FSharpParseFileResults) 
+        (symbol: FSharpMemberOrFunctionOrValue)
+        (symbolUse: FSharpSymbolUse) =
+        
+        let isNotAnnotatedManually = 
+            not (parseFileResults.IsTypeAnnotationGivenAtPosition symbolUse.Range.Start)
+
+        let isNotAfterDot = 
+            symbolUse.IsFromDefinition 
+            && not symbol.IsMemberThisValue
+
+        let isNotTypeAlias = 
+            not symbol.IsConstructorThisValue
+        
+        symbol.IsValue // we'll be adding other stuff gradually here
+        && isNotAnnotatedManually
+        && isNotAfterDot
+        && isNotTypeAlias
+
+    let private getHintParts
+        (symbol: FSharpMemberOrFunctionOrValue) 
+        (symbolUse: FSharpSymbolUse) =
+        
+        match symbol.GetReturnTypeLayout symbolUse.DisplayContext with
+        | Some typeInfo -> 
+            let colon = TaggedText(TextTag.Text, ": ")
+            Seq.append (seq { colon }) typeInfo
+        
+        // not sure when this can happen but better safe than sorry
+        | None -> 
+            Seq.empty
+        
+    let private getHintsForSymbol parseResults (symbolUse: FSharpSymbolUse) =
+        match symbolUse.Symbol with
+        | :? FSharpMemberOrFunctionOrValue as mfvSymbol 
+          when isValidForHint parseResults mfvSymbol symbolUse ->
+            
+            seq { {
+                Range = symbolUse.Range
+                Parts = getHintParts mfvSymbol symbolUse
+            } }
+        
+        // we'll be adding other stuff gradually here
+        | _ -> 
+            Seq.empty
+
+    let getHintsForDocument (document: Document) userOpName cancellationToken = 
+        task {
+            if isSignatureFile document.FilePath
+            then 
+                return Seq.empty
+            else
+                let! parseResults, checkResults = 
+                    document.GetFSharpParseAndCheckResultsAsync userOpName 
+                
+                return 
+                    checkResults.GetAllUsesOfAllSymbolsInFile cancellationToken
+                    |> Seq.collect (getHintsForSymbol parseResults)
+        }
diff --git a/vsintegration/src/FSharp.Editor/Hints/NativeToRoslynHintConverter.fs b/vsintegration/src/FSharp.Editor/Hints/NativeToRoslynHintConverter.fs
new file mode 100644
index 00000000000..9396d7d9256
--- /dev/null
+++ b/vsintegration/src/FSharp.Editor/Hints/NativeToRoslynHintConverter.fs
@@ -0,0 +1,27 @@
+﻿// Copyright (c) Microsoft Corporation.  All Rights Reserved.  See License.txt in the project root for license information.
+
+namespace Microsoft.VisualStudio.FSharp.Editor.Hints
+
+open System.Collections.Immutable
+open Microsoft.CodeAnalysis.Text
+open Microsoft.CodeAnalysis.ExternalAccess.FSharp.InlineHints
+open Microsoft.VisualStudio.FSharp.Editor
+open FSharp.Compiler.Text
+open HintService
+
+module NativeToRoslynHintConverter =
+
+    let rangeToSpan range sourceText = 
+        let symbolSpan = RoslynHelpers.FSharpRangeToTextSpan(sourceText, range)
+        let overshadowLength = 0 // anything >0 means overlaying the code
+        TextSpan(symbolSpan.End, overshadowLength)
+
+    let nativeToRoslynText (taggedText: TaggedText) = 
+        let tag = RoslynHelpers.roslynTag taggedText.Tag
+        let text = taggedText.Text
+        RoslynTaggedText(tag, text)
+
+    let convert sourceText hint =
+        let span = rangeToSpan hint.Range sourceText
+        let displayParts = hint.Parts |> Seq.map nativeToRoslynText
+        FSharpInlineHint(span, displayParts.ToImmutableArray())
\ No newline at end of file
diff --git a/vsintegration/src/FSharp.Editor/Hints/RoslynAdapter.fs b/vsintegration/src/FSharp.Editor/Hints/RoslynAdapter.fs
new file mode 100644
index 00000000000..f8971a9e290
--- /dev/null
+++ b/vsintegration/src/FSharp.Editor/Hints/RoslynAdapter.fs
@@ -0,0 +1,38 @@
+﻿// Copyright (c) Microsoft Corporation.  All Rights Reserved.  See License.txt in the project root for license information.
+
+namespace Microsoft.VisualStudio.FSharp.Editor.Hints
+
+open System.Collections.Immutable
+open System.ComponentModel.Composition
+open Microsoft.CodeAnalysis.ExternalAccess.FSharp.InlineHints
+open Microsoft.VisualStudio.FSharp.Editor
+
+// So the Roslyn interface is called IFSharpInlineHintsService
+// but our implementation is called just HintsService.
+// That's because we'll likely use this API for things other than inline hints,
+// e.g. signature hints above the line, pipeline hints on the side and so on.
+
+[<Export(typeof<IFSharpInlineHintsService>)>]
+type internal RoslynAdapter 
+    [<ImportingConstructor>]
+    (settings: EditorOptions) =
+
+    static let userOpName = "Hints"
+
+    interface IFSharpInlineHintsService with
+        member _.GetInlineHintsAsync(document, _, cancellationToken) =
+            task {
+                if not settings.Advanced.IsInlineHintsEnabled 
+                then return ImmutableArray.Empty
+                
+                else
+                    let! sourceText = document.GetTextAsync cancellationToken
+                    let! nativeHints = 
+                        HintService.getHintsForDocument document userOpName cancellationToken
+                
+                    let roslynHints = 
+                        nativeHints 
+                        |> Seq.map (NativeToRoslynHintConverter.convert sourceText)
+                
+                    return roslynHints.ToImmutableArray()
+            }
diff --git a/vsintegration/src/FSharp.Editor/Options/EditorOptions.fs b/vsintegration/src/FSharp.Editor/Options/EditorOptions.fs
index e6c4b202d8e..e9a05b99c7a 100644
--- a/vsintegration/src/FSharp.Editor/Options/EditorOptions.fs
+++ b/vsintegration/src/FSharp.Editor/Options/EditorOptions.fs
@@ -85,10 +85,12 @@ type LensOptions =
 [<CLIMutable>]
 type AdvancedOptions =
     { IsBlockStructureEnabled: bool
-      IsOutliningEnabled: bool }
+      IsOutliningEnabled: bool
+      IsInlineHintsEnabled: bool }
     static member Default =
       { IsBlockStructureEnabled = true
-        IsOutliningEnabled = true }
+        IsOutliningEnabled = true
+        IsInlineHintsEnabled = false }
 
 [<CLIMutable>]
 type FormattingOptions =
diff --git a/vsintegration/src/FSharp.UIResources/AdvancedOptionsControl.xaml b/vsintegration/src/FSharp.UIResources/AdvancedOptionsControl.xaml
index 7126d6efb8b..4e80e56c6f4 100644
--- a/vsintegration/src/FSharp.UIResources/AdvancedOptionsControl.xaml
+++ b/vsintegration/src/FSharp.UIResources/AdvancedOptionsControl.xaml
@@ -24,6 +24,10 @@
                     <CheckBox x:Name="toggleOutloning" IsChecked="{Binding IsOutliningEnabled}"
                               Content="{x:Static local:Strings.Show_Outlining}"/>
                 </GroupBox>
+                <GroupBox Header="{x:Static local:Strings.Inline_Hints}">
+                    <CheckBox x:Name="toggleInlineHints" IsChecked="{Binding IsInlineHintsEnabled}"
+                              Content="{x:Static local:Strings.Show_Inline_Hints}"/>
+                </GroupBox>
             </StackPanel>
         </ScrollViewer>
     </Grid>
diff --git a/vsintegration/src/FSharp.UIResources/Strings.Designer.cs b/vsintegration/src/FSharp.UIResources/Strings.Designer.cs
index d7e8ec6a86c..23f3b723204 100644
--- a/vsintegration/src/FSharp.UIResources/Strings.Designer.cs
+++ b/vsintegration/src/FSharp.UIResources/Strings.Designer.cs
@@ -204,6 +204,15 @@ public static string Format_on_paste {
             }
         }
         
+        /// <summary>
+        ///   Looks up a localized string similar to Inline Hints.
+        /// </summary>
+        public static string Inline_Hints {
+            get {
+                return ResourceManager.GetString("Inline_Hints", resourceCulture);
+            }
+        }
+
         /// <summary>
         ///   Looks up a localized string similar to IntelliSense Performance Options.
         /// </summary>
@@ -330,6 +339,15 @@ public static string Show_guides {
             }
         }
         
+        /// <summary>
+        ///   Looks up a localized string similar to Display inline type hints (experimental).
+        /// </summary>
+        public static string Show_Inline_Hints {
+            get {
+                return ResourceManager.GetString("Show_Inline_Hints", resourceCulture);
+            }
+        }
+
         /// <summary>
         ///   Looks up a localized string similar to S_how navigation links as.
         /// </summary>
diff --git a/vsintegration/src/FSharp.UIResources/Strings.resx b/vsintegration/src/FSharp.UIResources/Strings.resx
index a55acfe0290..e6f9fccd9da 100644
--- a/vsintegration/src/FSharp.UIResources/Strings.resx
+++ b/vsintegration/src/FSharp.UIResources/Strings.resx
@@ -189,6 +189,12 @@
   <data name="Show_Outlining" xml:space="preserve">
     <value>Show outlining and collapsible nodes for F# code</value>
   </data>
+  <data name="Inline_Hints" xml:space="preserve">
+    <value>Inline Hints</value>
+  </data>
+  <data name="Show_Inline_Hints" xml:space="preserve">
+    <value>Display inline type hints (experimental)</value>
+  </data>
   <data name="Time_until_stale_completion" xml:space="preserve">
     <value>Time until stale results are used (in milliseconds)</value>
   </data>
diff --git a/vsintegration/src/FSharp.UIResources/xlf/Strings.cs.xlf b/vsintegration/src/FSharp.UIResources/xlf/Strings.cs.xlf
index 465126331dd..2c2fe934b7c 100644
--- a/vsintegration/src/FSharp.UIResources/xlf/Strings.cs.xlf
+++ b/vsintegration/src/FSharp.UIResources/xlf/Strings.cs.xlf
@@ -7,6 +7,11 @@
         <target state="translated">Vždy umístit otevřené příkazy na nejvyšší úroveň</target>
         <note />
       </trans-unit>
+      <trans-unit id="Inline_Hints">
+        <source>Inline Hints</source>
+        <target state="new">Inline Hints</target>
+        <note />
+      </trans-unit>
       <trans-unit id="Lens">
         <source>Lens</source>
         <target state="translated">Lens</target>
@@ -77,6 +82,11 @@
         <target state="new">Parallelization (requires restart)</target>
         <note />
       </trans-unit>
+      <trans-unit id="Show_Inline_Hints">
+        <source>Display inline type hints (experimental)</source>
+        <target state="new">Display inline type hints (experimental)</target>
+        <note />
+      </trans-unit>
       <trans-unit id="Show_all_symbols">
         <source>Show s_ymbols in unopened namespaces</source>
         <target state="translated">Zobrazit s_ymboly v neotevřených oborech názvů</target>
diff --git a/vsintegration/src/FSharp.UIResources/xlf/Strings.de.xlf b/vsintegration/src/FSharp.UIResources/xlf/Strings.de.xlf
index 19dbdecc591..b946628c022 100644
--- a/vsintegration/src/FSharp.UIResources/xlf/Strings.de.xlf
+++ b/vsintegration/src/FSharp.UIResources/xlf/Strings.de.xlf
@@ -7,6 +7,11 @@
         <target state="translated">open-Anweisungen immer an oberster Ebene platzieren</target>
         <note />
       </trans-unit>
+      <trans-unit id="Inline_Hints">
+        <source>Inline Hints</source>
+        <target state="new">Inline Hints</target>
+        <note />
+      </trans-unit>
       <trans-unit id="Lens">
         <source>Lens</source>
         <target state="translated">Lens</target>
@@ -77,6 +82,11 @@
         <target state="new">Parallelization (requires restart)</target>
         <note />
       </trans-unit>
+      <trans-unit id="Show_Inline_Hints">
+        <source>Display inline type hints (experimental)</source>
+        <target state="new">Display inline type hints (experimental)</target>
+        <note />
+      </trans-unit>
       <trans-unit id="Show_all_symbols">
         <source>Show s_ymbols in unopened namespaces</source>
         <target state="translated">S_ymbole in nicht geöffneten Namespaces anzeigen</target>
diff --git a/vsintegration/src/FSharp.UIResources/xlf/Strings.es.xlf b/vsintegration/src/FSharp.UIResources/xlf/Strings.es.xlf
index 593de70499a..9212a40cb0b 100644
--- a/vsintegration/src/FSharp.UIResources/xlf/Strings.es.xlf
+++ b/vsintegration/src/FSharp.UIResources/xlf/Strings.es.xlf
@@ -7,6 +7,11 @@
         <target state="translated">Colocar siempre las instrucciones open en el nivel superior</target>
         <note />
       </trans-unit>
+      <trans-unit id="Inline_Hints">
+        <source>Inline Hints</source>
+        <target state="new">Inline Hints</target>
+        <note />
+      </trans-unit>
       <trans-unit id="Lens">
         <source>Lens</source>
         <target state="translated">Lens</target>
@@ -77,6 +82,11 @@
         <target state="new">Parallelization (requires restart)</target>
         <note />
       </trans-unit>
+      <trans-unit id="Show_Inline_Hints">
+        <source>Display inline type hints (experimental)</source>
+        <target state="new">Display inline type hints (experimental)</target>
+        <note />
+      </trans-unit>
       <trans-unit id="Show_all_symbols">
         <source>Show s_ymbols in unopened namespaces</source>
         <target state="translated">Mostrar sím_bolos en espacios de nombres sin abrir</target>
diff --git a/vsintegration/src/FSharp.UIResources/xlf/Strings.fr.xlf b/vsintegration/src/FSharp.UIResources/xlf/Strings.fr.xlf
index ac9837fd02a..5fa8f1caea2 100644
--- a/vsintegration/src/FSharp.UIResources/xlf/Strings.fr.xlf
+++ b/vsintegration/src/FSharp.UIResources/xlf/Strings.fr.xlf
@@ -7,6 +7,11 @@
         <target state="translated">Placer toujours les instructions open au niveau supérieur</target>
         <note />
       </trans-unit>
+      <trans-unit id="Inline_Hints">
+        <source>Inline Hints</source>
+        <target state="new">Inline Hints</target>
+        <note />
+      </trans-unit>
       <trans-unit id="Lens">
         <source>Lens</source>
         <target state="translated">Lens</target>
@@ -77,6 +82,11 @@
         <target state="new">Parallelization (requires restart)</target>
         <note />
       </trans-unit>
+      <trans-unit id="Show_Inline_Hints">
+        <source>Display inline type hints (experimental)</source>
+        <target state="new">Display inline type hints (experimental)</target>
+        <note />
+      </trans-unit>
       <trans-unit id="Show_all_symbols">
         <source>Show s_ymbols in unopened namespaces</source>
         <target state="translated">Afficher les sym_boles dans les espaces de noms non ouverts</target>
diff --git a/vsintegration/src/FSharp.UIResources/xlf/Strings.it.xlf b/vsintegration/src/FSharp.UIResources/xlf/Strings.it.xlf
index b1cbcdc1cda..a5732c33ea3 100644
--- a/vsintegration/src/FSharp.UIResources/xlf/Strings.it.xlf
+++ b/vsintegration/src/FSharp.UIResources/xlf/Strings.it.xlf
@@ -7,6 +7,11 @@
         <target state="translated">Inserisci sempre le istruzioni OPEN al primo livello</target>
         <note />
       </trans-unit>
+      <trans-unit id="Inline_Hints">
+        <source>Inline Hints</source>
+        <target state="new">Inline Hints</target>
+        <note />
+      </trans-unit>
       <trans-unit id="Lens">
         <source>Lens</source>
         <target state="translated">Lens</target>
@@ -77,6 +82,11 @@
         <target state="new">Parallelization (requires restart)</target>
         <note />
       </trans-unit>
+      <trans-unit id="Show_Inline_Hints">
+        <source>Display inline type hints (experimental)</source>
+        <target state="new">Display inline type hints (experimental)</target>
+        <note />
+      </trans-unit>
       <trans-unit id="Show_all_symbols">
         <source>Show s_ymbols in unopened namespaces</source>
         <target state="translated">Mostra si_mboli in spazi dei nomi non aperti</target>
diff --git a/vsintegration/src/FSharp.UIResources/xlf/Strings.ja.xlf b/vsintegration/src/FSharp.UIResources/xlf/Strings.ja.xlf
index 1d40d97e0e7..30a7d45df9f 100644
--- a/vsintegration/src/FSharp.UIResources/xlf/Strings.ja.xlf
+++ b/vsintegration/src/FSharp.UIResources/xlf/Strings.ja.xlf
@@ -7,6 +7,11 @@
         <target state="translated">Open ステートメントを常に最上位に配置する</target>
         <note />
       </trans-unit>
+      <trans-unit id="Inline_Hints">
+        <source>Inline Hints</source>
+        <target state="new">Inline Hints</target>
+        <note />
+      </trans-unit>
       <trans-unit id="Lens">
         <source>Lens</source>
         <target state="translated">Lens</target>
@@ -77,6 +82,11 @@
         <target state="new">Parallelization (requires restart)</target>
         <note />
       </trans-unit>
+      <trans-unit id="Show_Inline_Hints">
+        <source>Display inline type hints (experimental)</source>
+        <target state="new">Display inline type hints (experimental)</target>
+        <note />
+      </trans-unit>
       <trans-unit id="Show_all_symbols">
         <source>Show s_ymbols in unopened namespaces</source>
         <target state="translated">開かれていない名前空間の記号を表示する(_Y)</target>
diff --git a/vsintegration/src/FSharp.UIResources/xlf/Strings.ko.xlf b/vsintegration/src/FSharp.UIResources/xlf/Strings.ko.xlf
index 42d4f8d5b2f..ef59262a118 100644
--- a/vsintegration/src/FSharp.UIResources/xlf/Strings.ko.xlf
+++ b/vsintegration/src/FSharp.UIResources/xlf/Strings.ko.xlf
@@ -7,6 +7,11 @@
         <target state="translated">항상 최상위에 open 문 배치</target>
         <note />
       </trans-unit>
+      <trans-unit id="Inline_Hints">
+        <source>Inline Hints</source>
+        <target state="new">Inline Hints</target>
+        <note />
+      </trans-unit>
       <trans-unit id="Lens">
         <source>Lens</source>
         <target state="translated">Lens</target>
@@ -77,6 +82,11 @@
         <target state="new">Parallelization (requires restart)</target>
         <note />
       </trans-unit>
+      <trans-unit id="Show_Inline_Hints">
+        <source>Display inline type hints (experimental)</source>
+        <target state="new">Display inline type hints (experimental)</target>
+        <note />
+      </trans-unit>
       <trans-unit id="Show_all_symbols">
         <source>Show s_ymbols in unopened namespaces</source>
         <target state="translated">열려 있지 않은 네임스페이스에 기호 표시(_Y)</target>
diff --git a/vsintegration/src/FSharp.UIResources/xlf/Strings.pl.xlf b/vsintegration/src/FSharp.UIResources/xlf/Strings.pl.xlf
index 80c575eb707..b388b74d48e 100644
--- a/vsintegration/src/FSharp.UIResources/xlf/Strings.pl.xlf
+++ b/vsintegration/src/FSharp.UIResources/xlf/Strings.pl.xlf
@@ -7,6 +7,11 @@
         <target state="translated">Zawsze umieszczaj otwarte instrukcje na najwyższym poziomie</target>
         <note />
       </trans-unit>
+      <trans-unit id="Inline_Hints">
+        <source>Inline Hints</source>
+        <target state="new">Inline Hints</target>
+        <note />
+      </trans-unit>
       <trans-unit id="Lens">
         <source>Lens</source>
         <target state="translated">Lens</target>
@@ -77,6 +82,11 @@
         <target state="new">Parallelization (requires restart)</target>
         <note />
       </trans-unit>
+      <trans-unit id="Show_Inline_Hints">
+        <source>Display inline type hints (experimental)</source>
+        <target state="new">Display inline type hints (experimental)</target>
+        <note />
+      </trans-unit>
       <trans-unit id="Show_all_symbols">
         <source>Show s_ymbols in unopened namespaces</source>
         <target state="translated">Pokaż s_ymbole w nieotwartych przestrzeniach nazw</target>
diff --git a/vsintegration/src/FSharp.UIResources/xlf/Strings.pt-BR.xlf b/vsintegration/src/FSharp.UIResources/xlf/Strings.pt-BR.xlf
index ed7bd886e92..43082d7a468 100644
--- a/vsintegration/src/FSharp.UIResources/xlf/Strings.pt-BR.xlf
+++ b/vsintegration/src/FSharp.UIResources/xlf/Strings.pt-BR.xlf
@@ -7,6 +7,11 @@
         <target state="translated">Sempre coloque as instruções abertas no nível superior</target>
         <note />
       </trans-unit>
+      <trans-unit id="Inline_Hints">
+        <source>Inline Hints</source>
+        <target state="new">Inline Hints</target>
+        <note />
+      </trans-unit>
       <trans-unit id="Lens">
         <source>Lens</source>
         <target state="translated">Lens</target>
@@ -77,6 +82,11 @@
         <target state="new">Parallelization (requires restart)</target>
         <note />
       </trans-unit>
+      <trans-unit id="Show_Inline_Hints">
+        <source>Display inline type hints (experimental)</source>
+        <target state="new">Display inline type hints (experimental)</target>
+        <note />
+      </trans-unit>
       <trans-unit id="Show_all_symbols">
         <source>Show s_ymbols in unopened namespaces</source>
         <target state="translated">Mostrar s_ímbolos em namespaces não abertos</target>
diff --git a/vsintegration/src/FSharp.UIResources/xlf/Strings.ru.xlf b/vsintegration/src/FSharp.UIResources/xlf/Strings.ru.xlf
index 2b61ff39466..dfe465c0259 100644
--- a/vsintegration/src/FSharp.UIResources/xlf/Strings.ru.xlf
+++ b/vsintegration/src/FSharp.UIResources/xlf/Strings.ru.xlf
@@ -7,6 +7,11 @@
         <target state="translated">Всегда располагайте открытые операторы на верхнем уровне</target>
         <note />
       </trans-unit>
+      <trans-unit id="Inline_Hints">
+        <source>Inline Hints</source>
+        <target state="new">Inline Hints</target>
+        <note />
+      </trans-unit>
       <trans-unit id="Lens">
         <source>Lens</source>
         <target state="translated">Lens</target>
@@ -77,6 +82,11 @@
         <target state="new">Parallelization (requires restart)</target>
         <note />
       </trans-unit>
+      <trans-unit id="Show_Inline_Hints">
+        <source>Display inline type hints (experimental)</source>
+        <target state="new">Display inline type hints (experimental)</target>
+        <note />
+      </trans-unit>
       <trans-unit id="Show_all_symbols">
         <source>Show s_ymbols in unopened namespaces</source>
         <target state="translated">По_казать символы в неоткрытых пространствах имен</target>
diff --git a/vsintegration/src/FSharp.UIResources/xlf/Strings.tr.xlf b/vsintegration/src/FSharp.UIResources/xlf/Strings.tr.xlf
index 031215b1fdc..7717e353c22 100644
--- a/vsintegration/src/FSharp.UIResources/xlf/Strings.tr.xlf
+++ b/vsintegration/src/FSharp.UIResources/xlf/Strings.tr.xlf
@@ -7,6 +7,11 @@
         <target state="translated">Açık deyimleri her zaman en üst düzeye yerleştir</target>
         <note />
       </trans-unit>
+      <trans-unit id="Inline_Hints">
+        <source>Inline Hints</source>
+        <target state="new">Inline Hints</target>
+        <note />
+      </trans-unit>
       <trans-unit id="Lens">
         <source>Lens</source>
         <target state="translated">Lens</target>
@@ -77,6 +82,11 @@
         <target state="new">Parallelization (requires restart)</target>
         <note />
       </trans-unit>
+      <trans-unit id="Show_Inline_Hints">
+        <source>Display inline type hints (experimental)</source>
+        <target state="new">Display inline type hints (experimental)</target>
+        <note />
+      </trans-unit>
       <trans-unit id="Show_all_symbols">
         <source>Show s_ymbols in unopened namespaces</source>
         <target state="translated">Açılmamış ad alanlarında s_embolleri göster</target>
diff --git a/vsintegration/src/FSharp.UIResources/xlf/Strings.zh-Hans.xlf b/vsintegration/src/FSharp.UIResources/xlf/Strings.zh-Hans.xlf
index f78d543557e..7e5b5761e6e 100644
--- a/vsintegration/src/FSharp.UIResources/xlf/Strings.zh-Hans.xlf
+++ b/vsintegration/src/FSharp.UIResources/xlf/Strings.zh-Hans.xlf
@@ -7,6 +7,11 @@
         <target state="translated">始终在顶层放置 open 语句</target>
         <note />
       </trans-unit>
+      <trans-unit id="Inline_Hints">
+        <source>Inline Hints</source>
+        <target state="new">Inline Hints</target>
+        <note />
+      </trans-unit>
       <trans-unit id="Lens">
         <source>Lens</source>
         <target state="translated">Lens</target>
@@ -77,6 +82,11 @@
         <target state="new">Parallelization (requires restart)</target>
         <note />
       </trans-unit>
+      <trans-unit id="Show_Inline_Hints">
+        <source>Display inline type hints (experimental)</source>
+        <target state="new">Display inline type hints (experimental)</target>
+        <note />
+      </trans-unit>
       <trans-unit id="Show_all_symbols">
         <source>Show s_ymbols in unopened namespaces</source>
         <target state="translated">显示未打开的命名空间中的符号(_Y)</target>
diff --git a/vsintegration/src/FSharp.UIResources/xlf/Strings.zh-Hant.xlf b/vsintegration/src/FSharp.UIResources/xlf/Strings.zh-Hant.xlf
index 71eac224dc4..c24a02fb051 100644
--- a/vsintegration/src/FSharp.UIResources/xlf/Strings.zh-Hant.xlf
+++ b/vsintegration/src/FSharp.UIResources/xlf/Strings.zh-Hant.xlf
@@ -7,6 +7,11 @@
         <target state="translated">一律將 open 陳述式放在最上層</target>
         <note />
       </trans-unit>
+      <trans-unit id="Inline_Hints">
+        <source>Inline Hints</source>
+        <target state="new">Inline Hints</target>
+        <note />
+      </trans-unit>
       <trans-unit id="Lens">
         <source>Lens</source>
         <target state="translated">Lens</target>
@@ -77,6 +82,11 @@
         <target state="new">Parallelization (requires restart)</target>
         <note />
       </trans-unit>
+      <trans-unit id="Show_Inline_Hints">
+        <source>Display inline type hints (experimental)</source>
+        <target state="new">Display inline type hints (experimental)</target>
+        <note />
+      </trans-unit>
       <trans-unit id="Show_all_symbols">
         <source>Show s_ymbols in unopened namespaces</source>
         <target state="translated">顯示未開啟之命名空間中的符號(_Y)</target>
diff --git a/vsintegration/tests/UnitTests/HintServiceTests.fs b/vsintegration/tests/UnitTests/HintServiceTests.fs
new file mode 100644
index 00000000000..c80b57641c6
--- /dev/null
+++ b/vsintegration/tests/UnitTests/HintServiceTests.fs
@@ -0,0 +1,104 @@
+// Copyright (c) Microsoft Corporation.  All Rights Reserved.  See License.txt in the project root for license information.
+
+namespace VisualFSharp.UnitTests.Editor.Hints
+
+open NUnit.Framework
+open HintTestFramework
+
+module HintServiceTests =
+
+[<Test>]
+let ``Type hints are not shown in signature files`` () =
+    let fsiCode = """
+module Test
+
+val numbers: int[]
+"""
+    let fsCode = """
+module Test
+
+let numbers = [|42|]
+"""
+    let fsiDocument, _ = getFsiAndFsDocuments fsiCode fsCode
+
+    let result = getHints fsiDocument
+    
+    Assert.IsEmpty(result)
+
+[<Test>]
+let ``Type hint is shown for a let binding`` () =
+    let code = """
+type Song = { Artist: string; Title: string }
+
+let s = { Artist = "Moby"; Title = "Porcelain" }
+"""
+    let document = getFsDocument code
+    let expected = [{ Content = ": Song"; Location = (3, 6) }]
+
+    let actual = getHints document
+
+    Assert.AreEqual(expected, actual)
+
+[<Test>]
+let ``Type hint is not shown for a let binding when the type is manually specified`` () =
+    let code = """
+type Song = { Artist: string; Title: string }
+
+let s: Song = { Artist = "Moby"; Title = "Porcelain" }
+"""
+    let document = getFsDocument code
+    
+    let result = getHints document
+    
+    Assert.IsEmpty(result)
+
+[<Test>]
+let ``Type hint is shown for a parameter`` () =
+    let code = """
+type Song = { Artist: string; Title: string }
+
+let whoSings s = s.Artist
+"""
+    let document = getFsDocument code
+    let expected = [{ Content = ": Song"; Location = (3, 15) }]
+    
+    let actual = getHints document
+
+    Assert.AreEqual(expected, actual)
+
+[<Test>]
+let ``Type hint is not shown for a parameter when the type is manually specified`` () =
+    let code = """
+type Song = { Artist: string; Title: string }
+
+let whoSings (s: Song) = s.Artist
+"""
+    let document = getFsDocument code
+    
+    let result = getHints document
+    
+    Assert.IsEmpty(result)
+
+[<Test>] // here we don't want a hint after "this"
+let ``Type hint is not shown for type self-identifiers`` () =
+    let code = """
+type Song() =
+    member this.GetName() = "Porcelain"
+"""
+    let document = getFsDocument code
+    
+    let result = getHints document
+
+    Assert.IsEmpty(result)
+
+[<Test>] // here we don't want a hint after "x"
+let ``Type hint is not shown for type aliases`` () =
+    let code = """
+type Song() as x =
+    member this.Name = "Porcelain"
+"""
+    let document = getFsDocument code
+
+    let result = getHints document
+
+    Assert.IsEmpty(result)
diff --git a/vsintegration/tests/UnitTests/HintTestFramework.fs b/vsintegration/tests/UnitTests/HintTestFramework.fs
new file mode 100644
index 00000000000..cf446c57247
--- /dev/null
+++ b/vsintegration/tests/UnitTests/HintTestFramework.fs
@@ -0,0 +1,53 @@
+// Copyright (c) Microsoft Corporation.  All Rights Reserved.  See License.txt in the project root for license information.
+
+namespace VisualFSharp.UnitTests.Editor.Hints
+
+open System.Threading
+open Microsoft.VisualStudio.FSharp.Editor
+open Microsoft.VisualStudio.FSharp.Editor.Hints
+open Microsoft.VisualStudio.FSharp.Editor.Hints.HintService
+open VisualFSharp.UnitTests.Editor
+open Microsoft.CodeAnalysis.Text
+
+module HintTestFramework =
+
+// another representation for extra convenience
+type TestHint = {
+    Content: string
+    Location: int * int
+}
+
+let private convert hint = 
+    let content = 
+        hint.Parts
+        |> Seq.map (fun hintPart -> hintPart.Text)
+        |> String.concat ""
+
+    // that's about different coordinate systems
+    // in tests, the most convenient is the one used in editor,
+    // hence this conversion
+    let location = (hint.Range.StartLine - 1, hint.Range.EndColumn + 1)
+
+    { Content = content 
+      Location = location }
+
+let getFsDocument code = 
+    use project = SingleFileProject code
+    let fileName = fst project.Files.Head
+    let document, _ = RoslynTestHelpers.CreateSingleDocumentSolution(fileName, code)
+    document
+
+let getFsiAndFsDocuments (fsiCode: string) (fsCode: string) = 
+    RoslynTestHelpers.CreateTwoDocumentSolution(
+        "test.fsi",
+        SourceText.From fsiCode,
+        "test.fs",
+        SourceText.From fsCode)
+
+let getHints document = 
+    task {
+        let! hints = HintService.getHintsForDocument document "test" CancellationToken.None
+        return hints |> Seq.map convert
+    }
+    |> Async.AwaitTask
+    |> Async.RunSynchronously
diff --git a/vsintegration/tests/UnitTests/Tests.RoslynHelpers.fs b/vsintegration/tests/UnitTests/Tests.RoslynHelpers.fs
index f7de265b87b..da0451837a6 100644
--- a/vsintegration/tests/UnitTests/Tests.RoslynHelpers.fs
+++ b/vsintegration/tests/UnitTests/Tests.RoslynHelpers.fs
@@ -273,7 +273,59 @@ type RoslynTestHelpers private () =
 
         document
 
+    static member CreateTwoDocumentSolution (filePath1, text1: SourceText, filePath2, text2: SourceText) =
+        let workspace = new AdhocWorkspace(TestHostServices())
+
+        let projId = ProjectId.CreateNewId()
+        let docId1 = DocumentId.CreateNewId(projId)
+        let docId2 = DocumentId.CreateNewId(projId)
+
+        let docInfo1 =
+            DocumentInfo.Create(
+                docId1,
+                filePath1, 
+                loader=TextLoader.From(text1.Container, VersionStamp.Create(DateTime.UtcNow)),
+                filePath=filePath1,
+                sourceCodeKind= SourceCodeKind.Regular)
+
+        let docInfo2 =
+            DocumentInfo.Create(
+                docId2,
+                filePath2, 
+                loader=TextLoader.From(text2.Container, VersionStamp.Create(DateTime.UtcNow)),
+                filePath=filePath2,
+                sourceCodeKind= SourceCodeKind.Regular)
+
+        let projFilePath = "C:\\test.fsproj"
+        let projInfo =
+            ProjectInfo.Create(
+                projId,
+                VersionStamp.Create(DateTime.UtcNow),
+                projFilePath, 
+                "test.dll", 
+                LanguageNames.FSharp,
+                documents = [docInfo1;docInfo2],
+                filePath = projFilePath
+            )
+
+        let solutionInfo = SolutionInfo.Create(SolutionId.CreateNewId(), VersionStamp.Create(DateTime.UtcNow), "test.sln", [projInfo])
+        let solution = workspace.AddSolution(solutionInfo)
+
+        workspace
+            .Services
+            .GetService<IFSharpWorkspaceService>()
+            .FSharpProjectOptionsManager
+            .SetCommandLineOptions(projId, [|filePath1;filePath2|], ImmutableArray.Empty)
+
+        let document1 = solution.GetProject(projId).GetDocument(docId1)
+        let document2 = solution.GetProject(projId).GetDocument(docId2)
+        document1, document2
+
     static member CreateSingleDocumentSolution (filePath, code: string, ?options) =
         let text = SourceText.From(code)
         RoslynTestHelpers.CreateSingleDocumentSolution(filePath, text, ?options = options), text
 
+    static member CreateTwoDocumentSolution (filePath1, code1: string, filePath2, code2: string) =
+        let text1 = SourceText.From code1
+        let text2 = SourceText.From code2
+        RoslynTestHelpers.CreateTwoDocumentSolution(filePath1, text1, filePath2, text2)
diff --git a/vsintegration/tests/UnitTests/VisualFSharp.UnitTests.fsproj b/vsintegration/tests/UnitTests/VisualFSharp.UnitTests.fsproj
index 542ec626ebd..15b068503d8 100644
--- a/vsintegration/tests/UnitTests/VisualFSharp.UnitTests.fsproj
+++ b/vsintegration/tests/UnitTests/VisualFSharp.UnitTests.fsproj
@@ -102,6 +102,12 @@
     <Compile Include="QuickInfoTests.fs">
       <Link>Editor\QuickInfoTests.fs</Link>
     </Compile>
+    <Compile Include="HintTestFramework.fs">
+      <Link>Editor\Hints\HintTestFramework.fs</Link>
+    </Compile>
+    <Compile Include="HintServiceTests.fs">
+      <Link>Editor\Hints\HintServiceTests.fs</Link>
+    </Compile>
     <Compile Include="GoToDefinitionServiceTests.fs">
       <Link>Editor\GoToDefinitionServiceTests.fs</Link>
     </Compile>

From 82ec1d034cce6890675ced535802f0d85d6325ce Mon Sep 17 00:00:00 2001
From: Petr <psfinaki@users.noreply.github.com>
Date: Mon, 24 Oct 2022 17:55:48 +0200
Subject: [PATCH 2/4] Update

---
 .../src/FSharp.Editor/Hints/HintService.fs      | 17 +++++++++--------
 1 file changed, 9 insertions(+), 8 deletions(-)

diff --git a/vsintegration/src/FSharp.Editor/Hints/HintService.fs b/vsintegration/src/FSharp.Editor/Hints/HintService.fs
index aaa2f23b5ff..34c790daa5e 100644
--- a/vsintegration/src/FSharp.Editor/Hints/HintService.fs
+++ b/vsintegration/src/FSharp.Editor/Hints/HintService.fs
@@ -13,7 +13,7 @@ module HintService =
     // Relatively convenient for testing
     type NativeHint = {
         Range: range
-        Parts: seq<TaggedText>
+        Parts: TaggedText list
     }
 
     let private isValidForHint 
@@ -43,36 +43,37 @@ module HintService =
         match symbol.GetReturnTypeLayout symbolUse.DisplayContext with
         | Some typeInfo -> 
             let colon = TaggedText(TextTag.Text, ": ")
-            Seq.append (seq { colon }) typeInfo
+            colon :: (typeInfo |> Array.toList)
         
         // not sure when this can happen but better safe than sorry
         | None -> 
-            Seq.empty
+            []
         
     let private getHintsForSymbol parseResults (symbolUse: FSharpSymbolUse) =
         match symbolUse.Symbol with
         | :? FSharpMemberOrFunctionOrValue as mfvSymbol 
           when isValidForHint parseResults mfvSymbol symbolUse ->
             
-            seq { {
+            [ {
                 Range = symbolUse.Range
                 Parts = getHintParts mfvSymbol symbolUse
-            } }
+            } ]
         
         // we'll be adding other stuff gradually here
         | _ -> 
-            Seq.empty
+            []
 
     let getHintsForDocument (document: Document) userOpName cancellationToken = 
         task {
             if isSignatureFile document.FilePath
             then 
-                return Seq.empty
+                return []
             else
                 let! parseResults, checkResults = 
                     document.GetFSharpParseAndCheckResultsAsync userOpName 
                 
                 return 
                     checkResults.GetAllUsesOfAllSymbolsInFile cancellationToken
-                    |> Seq.collect (getHintsForSymbol parseResults)
+                    |> Seq.toList
+                    |> List.collect (getHintsForSymbol parseResults)
         }

From 387c3302a616ee30b3bf9f78e2e6d917cadb44df Mon Sep 17 00:00:00 2001
From: Petr <psfinaki@users.noreply.github.com>
Date: Tue, 25 Oct 2022 13:54:09 +0200
Subject: [PATCH 3/4] Updates

---
 vsintegration/src/FSharp.Editor/Hints/HintService.fs  |  2 +-
 .../src/FSharp.Editor/Hints/RoslynAdapter.fs          |  6 +++---
 vsintegration/tests/UnitTests/HintServiceTests.fs     | 11 +++++++++++
 3 files changed, 15 insertions(+), 4 deletions(-)

diff --git a/vsintegration/src/FSharp.Editor/Hints/HintService.fs b/vsintegration/src/FSharp.Editor/Hints/HintService.fs
index 34c790daa5e..13edb5448d8 100644
--- a/vsintegration/src/FSharp.Editor/Hints/HintService.fs
+++ b/vsintegration/src/FSharp.Editor/Hints/HintService.fs
@@ -64,7 +64,7 @@ module HintService =
             []
 
     let getHintsForDocument (document: Document) userOpName cancellationToken = 
-        task {
+        async {
             if isSignatureFile document.FilePath
             then 
                 return []
diff --git a/vsintegration/src/FSharp.Editor/Hints/RoslynAdapter.fs b/vsintegration/src/FSharp.Editor/Hints/RoslynAdapter.fs
index f8971a9e290..01fde061024 100644
--- a/vsintegration/src/FSharp.Editor/Hints/RoslynAdapter.fs
+++ b/vsintegration/src/FSharp.Editor/Hints/RoslynAdapter.fs
@@ -21,12 +21,12 @@ type internal RoslynAdapter
 
     interface IFSharpInlineHintsService with
         member _.GetInlineHintsAsync(document, _, cancellationToken) =
-            task {
+            async {
                 if not settings.Advanced.IsInlineHintsEnabled 
                 then return ImmutableArray.Empty
                 
                 else
-                    let! sourceText = document.GetTextAsync cancellationToken
+                    let! sourceText = document.GetTextAsync cancellationToken |> Async.AwaitTask
                     let! nativeHints = 
                         HintService.getHintsForDocument document userOpName cancellationToken
                 
@@ -35,4 +35,4 @@ type internal RoslynAdapter
                         |> Seq.map (NativeToRoslynHintConverter.convert sourceText)
                 
                     return roslynHints.ToImmutableArray()
-            }
+            } |> RoslynHelpers.StartAsyncAsTask cancellationToken
diff --git a/vsintegration/tests/UnitTests/HintServiceTests.fs b/vsintegration/tests/UnitTests/HintServiceTests.fs
index c80b57641c6..f94954df4c9 100644
--- a/vsintegration/tests/UnitTests/HintServiceTests.fs
+++ b/vsintegration/tests/UnitTests/HintServiceTests.fs
@@ -25,6 +25,17 @@ let numbers = [|42|]
     
     Assert.IsEmpty(result)
 
+[<Test>]
+let ``Type hints are not shown for let-bound functions yet`` () =
+    let code = """
+let setConsoleOut = System.Console.SetOut
+"""
+    let document = getFsDocument code
+
+    let result = getHints document
+
+    Assert.IsEmpty(result)
+
 [<Test>]
 let ``Type hint is shown for a let binding`` () =
     let code = """

From 6b7cf88e0e7344d8c7eb84d1f4a4c9ce76cfaedc Mon Sep 17 00:00:00 2001
From: Petr <psfinaki@users.noreply.github.com>
Date: Tue, 25 Oct 2022 14:34:55 +0200
Subject: [PATCH 4/4] Update HintTestFramework.fs

---
 vsintegration/tests/UnitTests/HintTestFramework.fs | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/vsintegration/tests/UnitTests/HintTestFramework.fs b/vsintegration/tests/UnitTests/HintTestFramework.fs
index cf446c57247..87d4438abc3 100644
--- a/vsintegration/tests/UnitTests/HintTestFramework.fs
+++ b/vsintegration/tests/UnitTests/HintTestFramework.fs
@@ -45,9 +45,8 @@ let getFsiAndFsDocuments (fsiCode: string) (fsCode: string) =
         SourceText.From fsCode)
 
 let getHints document = 
-    task {
+    async {
         let! hints = HintService.getHintsForDocument document "test" CancellationToken.None
         return hints |> Seq.map convert
     }
-    |> Async.AwaitTask
     |> Async.RunSynchronously
