diff --git a/src/fsharp/service/FSharpParseFileResults.fs b/src/fsharp/service/FSharpParseFileResults.fs
index a68c6359d2b..24f31915cbd 100644
--- a/src/fsharp/service/FSharpParseFileResults.fs
+++ b/src/fsharp/service/FSharpParseFileResults.fs
@@ -334,6 +334,17 @@ type FSharpParseFileResults(diagnostics: FSharpDiagnostic[], input: ParsedInput,
None
| _ -> defaultTraverse expr })
+ member _.TryRangeOfExpressionBeingDereferencedContainingPos expressionPos =
+ SyntaxTraversal.Traverse(expressionPos, input, { new SyntaxVisitorBase<_>() with
+ member _.VisitExpr(_, _, defaultTraverse, expr) =
+ match expr with
+ | SynExpr.App(_, false, SynExpr.Ident funcIdent, expr, _) ->
+ if funcIdent.idText = "op_Dereference" && rangeContainsPos expr.Range expressionPos then
+ Some expr.Range
+ else
+ None
+ | _ -> defaultTraverse expr })
+
member _.FindParameterLocations pos =
ParameterLocations.Find(pos, input)
diff --git a/src/fsharp/service/FSharpParseFileResults.fsi b/src/fsharp/service/FSharpParseFileResults.fsi
index 3a530fc2050..9d3378a7b9a 100644
--- a/src/fsharp/service/FSharpParseFileResults.fsi
+++ b/src/fsharp/service/FSharpParseFileResults.fsi
@@ -46,6 +46,9 @@ type public FSharpParseFileResults =
///
member TryRangeOfRefCellDereferenceContainingPos: expressionPos: pos -> range option
+ /// Gets the range of an expression being dereferenced. For `!expr`, gives the range of `expr`
+ member TryRangeOfExpressionBeingDereferencedContainingPos: expressionPos: pos -> range option
+
/// Notable parse info for ParameterInfo at a given location
member FindParameterLocations: pos:pos -> ParameterLocations option
diff --git a/tests/FSharp.Compiler.Service.Tests/SurfaceArea.netstandard.fs b/tests/FSharp.Compiler.Service.Tests/SurfaceArea.netstandard.fs
index 51f59bb0a47..062bd21d080 100644
--- a/tests/FSharp.Compiler.Service.Tests/SurfaceArea.netstandard.fs
+++ b/tests/FSharp.Compiler.Service.Tests/SurfaceArea.netstandard.fs
@@ -1496,6 +1496,7 @@ FSharp.Compiler.CodeAnalysis.FSharpParseFileResults: Microsoft.FSharp.Core.FShar
FSharp.Compiler.CodeAnalysis.FSharpParseFileResults: Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.Text.Range] TryRangeOfNameOfNearestOuterBindingContainingPos(FSharp.Compiler.Text.Position)
FSharp.Compiler.CodeAnalysis.FSharpParseFileResults: Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.Text.Range] TryRangeOfRecordExpressionContainingPos(FSharp.Compiler.Text.Position)
FSharp.Compiler.CodeAnalysis.FSharpParseFileResults: Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.Text.Range] TryRangeOfRefCellDereferenceContainingPos(FSharp.Compiler.Text.Position)
+FSharp.Compiler.CodeAnalysis.FSharpParseFileResults: Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.Text.Range] TryRangeOfExpressionBeingDereferencedContainingPos(FSharp.Compiler.Text.Position)
FSharp.Compiler.CodeAnalysis.FSharpParseFileResults: Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.Text.Range] ValidateBreakpointLocation(FSharp.Compiler.Text.Position)
FSharp.Compiler.CodeAnalysis.FSharpParseFileResults: Microsoft.FSharp.Core.FSharpOption`1[Microsoft.FSharp.Collections.FSharpList`1[FSharp.Compiler.Text.Range]] GetAllArgumentsForFunctionApplicationAtPostion(FSharp.Compiler.Text.Position)
FSharp.Compiler.CodeAnalysis.FSharpParseFileResults: Microsoft.FSharp.Core.FSharpOption`1[System.Tuple`2[FSharp.Compiler.Syntax.Ident,System.Int32]] TryIdentOfPipelineContainingPosAndNumArgsApplied(FSharp.Compiler.Text.Position)
diff --git a/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj b/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj
index 9130c7573c7..1344d7e180d 100644
--- a/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj
+++ b/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj
@@ -90,6 +90,7 @@
+
diff --git a/vsintegration/src/FSharp.Editor/FSharp.Editor.resx b/vsintegration/src/FSharp.Editor/FSharp.Editor.resx
index 57bb282a098..0739ef50003 100644
--- a/vsintegration/src/FSharp.Editor/FSharp.Editor.resx
+++ b/vsintegration/src/FSharp.Editor/FSharp.Editor.resx
@@ -273,6 +273,9 @@
Use '<>' for inequality check
+
+ Use '.Value' to dereference expression
+
Add type annotation
diff --git a/vsintegration/src/FSharp.Editor/Refactor/ChangeDerefToValueRefactoring.fs b/vsintegration/src/FSharp.Editor/Refactor/ChangeDerefToValueRefactoring.fs
new file mode 100644
index 00000000000..7958133727b
--- /dev/null
+++ b/vsintegration/src/FSharp.Editor/Refactor/ChangeDerefToValueRefactoring.fs
@@ -0,0 +1,64 @@
+// 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 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
+
+[]
+type internal FSharpChangeDerefToValueRefactoring
+ []
+ (
+ checkerProvider: FSharpCheckerProvider,
+ projectInfoManager: FSharpProjectOptionsManager
+ ) =
+ inherit CodeRefactoringProvider()
+
+ static let userOpName = "ChangeDerefToValue"
+
+ override _.ComputeRefactoringsAsync context =
+ asyncMaybe {
+ let document = context.Document
+ let! parsingOptions, _ = projectInfoManager.TryGetOptionsForEditingDocumentOrProject(document, context.CancellationToken, userOpName)
+ let! sourceText = context.Document.GetTextAsync(context.CancellationToken)
+ let! parseResults = checkerProvider.Checker.ParseFile(document.FilePath, sourceText.ToFSharpSourceText(), parsingOptions, userOpName=userOpName) |> liftAsync
+
+ let selectionRange = RoslynHelpers.TextSpanToFSharpRange(document.FilePath, context.Span, sourceText)
+ let! derefRange = parseResults.TryRangeOfRefCellDereferenceContainingPos selectionRange.Start
+ let! exprRange = parseResults.TryRangeOfExpressionBeingDereferencedContainingPos selectionRange.Start
+
+ let combinedRange = Range.unionRanges derefRange exprRange
+ let! combinedSpan = RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, combinedRange)
+ let replacementString =
+ // Trim off the `!`
+ sourceText.GetSubText(combinedSpan).ToString().[1..] + ".Value"
+
+ let title = SR.UseValueInsteadOfDeref()
+
+ let getChangedText (sourceText: SourceText) =
+ sourceText.WithChanges(TextChange(combinedSpan, replacementString))
+
+ let codeAction =
+ CodeAction.Create(
+ title,
+ (fun (cancellationToken: CancellationToken) ->
+ async {
+ let! sourceText = context.Document.GetTextAsync(cancellationToken) |> Async.AwaitTask
+ return context.Document.WithText(getChangedText sourceText)
+ } |> RoslynHelpers.StartAsyncAsTask(cancellationToken)),
+ title)
+ context.RegisterRefactoring(codeAction)
+ }
+ |> Async.Ignore
+ |> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken)
diff --git a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.cs.xlf b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.cs.xlf
index 251a1388662..629ec4c2dc2 100644
--- a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.cs.xlf
+++ b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.cs.xlf
@@ -262,6 +262,11 @@
Negovat výraz pomocí not
+
+ Use '.Value' to dereference expression
+ Use '.Value' to dereference expression
+
+
Wrap expression in parentheses
Uzavřít výraz do závorek
diff --git a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.de.xlf b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.de.xlf
index 9249c295f7a..d1439f4fa4c 100644
--- a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.de.xlf
+++ b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.de.xlf
@@ -262,6 +262,11 @@
"not" zum Negieren eines Ausdruck verwenden
+
+ Use '.Value' to dereference expression
+ Use '.Value' to dereference expression
+
+
Wrap expression in parentheses
Ausdruck in Klammern einschließen
diff --git a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.es.xlf b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.es.xlf
index 4b002337fc9..8b9027c4da9 100644
--- a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.es.xlf
+++ b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.es.xlf
@@ -262,6 +262,11 @@
Usar "not" para negar la expresión
+
+ Use '.Value' to dereference expression
+ Use '.Value' to dereference expression
+
+
Wrap expression in parentheses
Encapsular la expresión entre paréntesis
diff --git a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.fr.xlf b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.fr.xlf
index 3f304fecda3..5d73604e920 100644
--- a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.fr.xlf
+++ b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.fr.xlf
@@ -262,6 +262,11 @@
Utiliser 'not' pour annuler l'expression
+
+ Use '.Value' to dereference expression
+ Use '.Value' to dereference expression
+
+
Wrap expression in parentheses
Mettre l'expression entre parenthèses
diff --git a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.it.xlf b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.it.xlf
index 5f2e37301c0..e35409a8448 100644
--- a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.it.xlf
+++ b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.it.xlf
@@ -262,6 +262,11 @@
Usare 'not' per negare l'espressione
+
+ Use '.Value' to dereference expression
+ Use '.Value' to dereference expression
+
+
Wrap expression in parentheses
Racchiudere l'espressione tra parentesi
diff --git a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.ja.xlf b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.ja.xlf
index d8e55789efe..942e9424613 100644
--- a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.ja.xlf
+++ b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.ja.xlf
@@ -262,6 +262,11 @@
式を否定するには 'not' を使用する
+
+ Use '.Value' to dereference expression
+ Use '.Value' to dereference expression
+
+
Wrap expression in parentheses
式をかっこで囲む
diff --git a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.ko.xlf b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.ko.xlf
index 1b3c41a2268..41224386c06 100644
--- a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.ko.xlf
+++ b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.ko.xlf
@@ -262,6 +262,11 @@
식을 부정하려면 'not' 사용
+
+ Use '.Value' to dereference expression
+ Use '.Value' to dereference expression
+
+
Wrap expression in parentheses
식을 괄호로 래핑
diff --git a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.pl.xlf b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.pl.xlf
index d9ea6371acd..a51c64408cd 100644
--- a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.pl.xlf
+++ b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.pl.xlf
@@ -262,6 +262,11 @@
Użyj operatora „not”, aby zanegować wyrażenie
+
+ Use '.Value' to dereference expression
+ Use '.Value' to dereference expression
+
+
Wrap expression in parentheses
Ujmij wyrażenie w nawiasy
diff --git a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.pt-BR.xlf b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.pt-BR.xlf
index b76c694378d..003d901383b 100644
--- a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.pt-BR.xlf
+++ b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.pt-BR.xlf
@@ -262,6 +262,11 @@
Use 'not' para negar a expressão
+
+ Use '.Value' to dereference expression
+ Use '.Value' to dereference expression
+
+
Wrap expression in parentheses
Coloque a expressão entre parênteses
diff --git a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.ru.xlf b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.ru.xlf
index 3f6596d7492..6ab6ffa5f2e 100644
--- a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.ru.xlf
+++ b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.ru.xlf
@@ -262,6 +262,11 @@
Используйте "not" для отрицания выражения.
+
+ Use '.Value' to dereference expression
+ Use '.Value' to dereference expression
+
+
Wrap expression in parentheses
Заключите выражение в круглые скобки.
diff --git a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.tr.xlf b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.tr.xlf
index 0d433910885..e290098742b 100644
--- a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.tr.xlf
+++ b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.tr.xlf
@@ -262,6 +262,11 @@
İfadeyi negatif yapmak için 'not' kullanın
+
+ Use '.Value' to dereference expression
+ Use '.Value' to dereference expression
+
+
Wrap expression in parentheses
İfadeyi parantez içinde sarmalayın
diff --git a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.zh-Hans.xlf b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.zh-Hans.xlf
index 020ff6e6b43..eec423b4781 100644
--- a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.zh-Hans.xlf
+++ b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.zh-Hans.xlf
@@ -262,6 +262,11 @@
使用 "not" 对表达式求反
+
+ Use '.Value' to dereference expression
+ Use '.Value' to dereference expression
+
+
Wrap expression in parentheses
将表达式用括号括起来
diff --git a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.zh-Hant.xlf b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.zh-Hant.xlf
index e6e56785150..b1d27931da5 100644
--- a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.zh-Hant.xlf
+++ b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.zh-Hant.xlf
@@ -262,6 +262,11 @@
使用 'not' 來否定運算式
+
+ Use '.Value' to dereference expression
+ Use '.Value' to dereference expression
+
+
Wrap expression in parentheses
使用括弧包裝運算式