diff --git a/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj b/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj
index 60e86c9eb96..7282aa2c4e2 100644
--- a/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj
+++ b/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj
@@ -99,6 +99,7 @@
+
diff --git a/vsintegration/src/FSharp.Editor/FSharp.Editor.resx b/vsintegration/src/FSharp.Editor/FSharp.Editor.resx
index 4b41551aac0..9c12f2a6519 100644
--- a/vsintegration/src/FSharp.Editor/FSharp.Editor.resx
+++ b/vsintegration/src/FSharp.Editor/FSharp.Editor.resx
@@ -352,6 +352,9 @@ Use live (unsaved) buffers for analysis
Convert C# 'using' to F# 'open'
+
+ Add explicit return type annotation
+
Remove unnecessary parentheses
diff --git a/vsintegration/src/FSharp.Editor/Refactor/AddExplicitReturnType.fs b/vsintegration/src/FSharp.Editor/Refactor/AddExplicitReturnType.fs
new file mode 100644
index 00000000000..c603939f9fe
--- /dev/null
+++ b/vsintegration/src/FSharp.Editor/Refactor/AddExplicitReturnType.fs
@@ -0,0 +1,109 @@
+// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information.
+
+namespace Microsoft.VisualStudio.FSharp.Editor
+
+open System
+open System.Composition
+open System.Threading
+open System.Threading.Tasks
+
+open FSharp.Compiler
+open FSharp.Compiler.CodeAnalysis
+open FSharp.Compiler.Symbols
+open FSharp.Compiler.Text
+open FSharp.Compiler.Syntax
+
+open Microsoft.CodeAnalysis.Text
+open Microsoft.CodeAnalysis.CodeRefactorings
+open Microsoft.CodeAnalysis.CodeActions
+open CancellableTasks
+
+[]
+type internal AddExplicitReturnType [] () =
+ inherit CodeRefactoringProvider()
+
+ static member isValidMethodWithoutTypeAnnotation (funcOrValue: FSharpMemberOrFunctionOrValue) (symbolUse: FSharpSymbolUse) (parseFileResults:FSharpParseFileResults) =
+ let isLambdaIfFunction =
+ funcOrValue.IsFunction
+ && parseFileResults.IsBindingALambdaAtPosition symbolUse.Range.Start
+
+ (not funcOrValue.IsValue || not isLambdaIfFunction)
+ && not (funcOrValue.ReturnParameter.Type.IsUnresolved)
+ && not (parseFileResults.IsTypeAnnotationGivenAtPosition symbolUse.Range.Start)
+
+
+ static member refactor (context:CodeRefactoringContext) (symbolUse:FSharpSymbolUse,memberFunc:FSharpMemberOrFunctionOrValue,symbolSpan) =
+ let typeString = memberFunc.FullType.FormatWithConstraints symbolUse.DisplayContext
+ let title = SR.AddExplicitReturnTypeAnnotation()
+
+ let getChangedText (sourceText: SourceText) =
+ let debugInfo = $"{sourceText} : {typeString} : {symbolSpan}"
+ debugInfo
+ let sub = sourceText.ToString(symbolSpan)
+
+ let newSub =
+ sub.Replace("=", $" :{memberFunc.ReturnParameter.Type.TypeDefinition.DisplayName}=")
+
+ sourceText.Replace(symbolSpan, newSub)
+
+ let codeActionFunc = (fun (cancellationToken: CancellationToken) ->
+ backgroundTask {
+ let! sourceText = context.Document.GetTextAsync(cancellationToken)
+ let changedText = getChangedText sourceText
+ return context.Document.WithText(changedText)
+ }
+ )
+ let codeAction =
+ CodeAction.Create(
+ title,
+ codeActionFunc,
+ title
+ )
+
+
+ do context.RegisterRefactoring(codeAction)
+
+ override _.ComputeRefactoringsAsync context =
+ backgroundTask {
+ let document = context.Document
+ let position = context.Span.Start
+ let! sourceText = document.GetTextAsync()
+ let textLine = sourceText.Lines.GetLineFromPosition position
+ let textLinePos = sourceText.Lines.GetLinePosition position
+ let fcsTextLineNumber = Line.fromZ textLinePos.Line
+
+ let! ct = Async.CancellationToken
+
+ let! lexerSymbol =
+ document.TryFindFSharpLexerSymbolAsync(position, SymbolLookupKind.Greedy, false, false, nameof (AddExplicitReturnType))
+
+
+ let! (parseFileResults,checkFileResults) =
+ document.GetFSharpParseAndCheckResultsAsync(nameof (AddExplicitReturnType))
+ |> CancellableTask.start ct
+
+ let res =
+ lexerSymbol
+ |> Option.bind(fun lexer ->
+ checkFileResults.GetSymbolUseAtLocation(
+ fcsTextLineNumber,
+ lexer.Ident.idRange.EndColumn,
+ textLine.ToString(),
+ lexer.FullIsland
+ ))
+ |> Option.bind(fun symbolUse->
+ match symbolUse.Symbol with
+ | :? FSharpMemberOrFunctionOrValue as v
+ when AddExplicitReturnType.isValidMethodWithoutTypeAnnotation v symbolUse parseFileResults->
+ Some (symbolUse,v)
+ | _ -> None
+ )
+ |> Option.bind(fun (symbolUse,memberFunc)->
+ match RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, symbolUse.Range) with
+ | Some span -> Some(symbolUse,memberFunc,span)
+ | None -> None
+ )
+ |> Option.map(fun (symbolUse,memberFunc,textSpan) -> AddExplicitReturnType.refactor context (symbolUse,memberFunc,textSpan))
+
+ return res
+ }
diff --git a/vsintegration/tests/FSharp.Editor.Tests/FSharp.Editor.Tests.fsproj b/vsintegration/tests/FSharp.Editor.Tests/FSharp.Editor.Tests.fsproj
index 77ce57f27e0..606768a9f64 100644
--- a/vsintegration/tests/FSharp.Editor.Tests/FSharp.Editor.Tests.fsproj
+++ b/vsintegration/tests/FSharp.Editor.Tests/FSharp.Editor.Tests.fsproj
@@ -12,6 +12,7 @@
+
@@ -71,6 +72,7 @@
+
diff --git a/vsintegration/tests/FSharp.Editor.Tests/Refactors/AddExplicitReturnType.fs b/vsintegration/tests/FSharp.Editor.Tests/Refactors/AddExplicitReturnType.fs
new file mode 100644
index 00000000000..14495b8ce07
--- /dev/null
+++ b/vsintegration/tests/FSharp.Editor.Tests/Refactors/AddExplicitReturnType.fs
@@ -0,0 +1,52 @@
+module FSharp.Editor.Tests.Refactors.AddExplicitReturnType
+
+open Microsoft.VisualStudio.FSharp.Editor
+open Xunit
+open System
+open System.Collections.Immutable
+open System.Text.RegularExpressions
+
+open Microsoft.CodeAnalysis
+open Microsoft.CodeAnalysis.CodeFixes
+open Microsoft.CodeAnalysis.Text
+open Microsoft.VisualStudio.FSharp.Editor
+open Microsoft.VisualStudio.FSharp.Editor.CancellableTasks
+
+open FSharp.Compiler.Diagnostics
+open FSharp.Editor.Tests.Helpers
+
+open Microsoft.CodeAnalysis.CodeRefactorings
+open NUnit.Framework
+
+
+[]
+let ``Refactor changes something`` () =
+ task {
+ let code =
+ """
+ let sum a b = a + b
+ """
+
+ let! ct = Async.CancellationToken
+
+ let sourceText = SourceText.From code
+ let document = RoslynTestHelpers.GetFsDocument code
+ let spanStart = code.IndexOf "sum"
+ let span = TextSpan(spanStart, 3)
+
+ let mutable refactorContext =
+ CodeRefactoringContext(document, span, Action(fun a -> ()), ct)
+
+ let refactorProvider = AddExplicitReturnType()
+ let expected = None
+
+ do! refactorProvider.ComputeRefactoringsAsync refactorContext
+ let newText = match refactorContext.TextDocument.TryGetText() with
+ | true,result -> result
+ | false,_ -> sourceText
+
+ let! text = refactorContext.TextDocument.GetTextAsync( ct)
+ Assert.AreNotEqual(sourceText.ToString(),newText.ToString(),"")
+
+ ()
+ }