diff --git a/src/fsharp/service/ServiceUntypedParse.fs b/src/fsharp/service/ServiceUntypedParse.fs
index bd24fef3fad..0513f0a2fa4 100755
--- a/src/fsharp/service/ServiceUntypedParse.fs
+++ b/src/fsharp/service/ServiceUntypedParse.fs
@@ -100,6 +100,24 @@ type FSharpParseFileResults(errors: FSharpErrorInfo[], input: ParsedInput option
member scope.ParseTree = input
+ member scope.TryRangeOfRefCellDereferenceContainingPos expressionPos =
+ match input with
+ | Some input ->
+ let res =
+ AstTraversal.Traverse(expressionPos, input, { new AstTraversal.AstVisitorBase<_>() 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 funcIdent.idRange
+ else
+ None
+ | _ -> defaultTraverse expr
+ })
+ res
+ | None ->
+ None
+
member scope.FindNoteworthyParamInfoLocations pos =
match input with
| Some input -> FSharpNoteworthyParamInfoLocations.Find(pos, input)
diff --git a/src/fsharp/service/ServiceUntypedParse.fsi b/src/fsharp/service/ServiceUntypedParse.fsi
index b88bc0efec3..96b783ecf3f 100755
--- a/src/fsharp/service/ServiceUntypedParse.fsi
+++ b/src/fsharp/service/ServiceUntypedParse.fsi
@@ -19,6 +19,13 @@ type public FSharpParseFileResults =
/// The syntax tree resulting from the parse
member ParseTree : ParsedInput option
+ ///
+ /// Given the position of an expression, attempts to find the range of the
+ /// '!' in a derefence operation of that expression, like:
+ /// '!expr', '!(expr)', etc.
+ ///
+ member TryRangeOfRefCellDereferenceContainingPos: expressionPos: pos -> Option
+
/// Notable parse info for ParameterInfo at a given location
member FindNoteworthyParamInfoLocations : pos:pos -> FSharpNoteworthyParamInfoLocations option
diff --git a/tests/FSharp.Compiler.Service.Tests/SurfaceArea.netstandard.fs b/tests/FSharp.Compiler.Service.Tests/SurfaceArea.netstandard.fs
index 42f826582fe..c409ba5c82a 100644
--- a/tests/FSharp.Compiler.Service.Tests/SurfaceArea.netstandard.fs
+++ b/tests/FSharp.Compiler.Service.Tests/SurfaceArea.netstandard.fs
@@ -22749,6 +22749,7 @@ FSharp.Compiler.SourceCodeServices.FSharpParseFileResults: Boolean get_ParseHadE
FSharp.Compiler.SourceCodeServices.FSharpParseFileResults: FSharp.Compiler.SourceCodeServices.FSharpErrorInfo[] Errors
FSharp.Compiler.SourceCodeServices.FSharpParseFileResults: FSharp.Compiler.SourceCodeServices.FSharpErrorInfo[] get_Errors()
FSharp.Compiler.SourceCodeServices.FSharpParseFileResults: FSharp.Compiler.SourceCodeServices.FSharpNavigationItems GetNavigationItems()
+FSharp.Compiler.SourceCodeServices.FSharpParseFileResults: Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.Range+range] TryRangeOfRefCellDereferenceContainingPos(pos)
FSharp.Compiler.SourceCodeServices.FSharpParseFileResults: Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.Range+range] ValidateBreakpointLocation(pos)
FSharp.Compiler.SourceCodeServices.FSharpParseFileResults: Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.SourceCodeServices.FSharpNoteworthyParamInfoLocations] FindNoteworthyParamInfoLocations(pos)
FSharp.Compiler.SourceCodeServices.FSharpParseFileResults: Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.SyntaxTree+ParsedInput] ParseTree
diff --git a/tests/service/ServiceUntypedParseTests.fs b/tests/service/ServiceUntypedParseTests.fs
index 18c7f7318d1..07d31f9a38e 100644
--- a/tests/service/ServiceUntypedParseTests.fs
+++ b/tests/service/ServiceUntypedParseTests.fs
@@ -305,3 +305,56 @@ type T =
new (x:int) = ()
"""
getTypeMemberRange source |> shouldEqual [ (3, 4), (3, 20) ]
+
+
+[]
+let ``TryRangeOfRefCellDereferenceContainingPos - simple``() =
+ let source = """
+let x = false
+let y = !x
+"""
+ let parseFileResults, _ = getParseAndCheckResults source
+ let res = parseFileResults.TryRangeOfRefCellDereferenceContainingPos (mkPos 3 9)
+ match res with
+ | Some res ->
+ res
+ |> tups
+ |> fst
+ |> shouldEqual (3, 8)
+ | None ->
+ Assert.Fail("No deref operator found in source.")
+
+[]
+let ``TryRangeOfRefCellDereferenceContainingPos - parens``() =
+ let source = """
+let x = false
+let y = !(x)
+"""
+ let parseFileResults, _ = getParseAndCheckResults source
+ let res = parseFileResults.TryRangeOfRefCellDereferenceContainingPos (mkPos 3 10)
+ match res with
+ | Some res ->
+ res
+ |> tups
+ |> fst
+ |> shouldEqual (3, 8)
+ | None ->
+ Assert.Fail("No deref operator found in source.")
+
+
+[]
+let ``TryRangeOfRefCellDereferenceContainingPos - binary expr``() =
+ let source = """
+let x = false
+let y = !(x = false)
+"""
+ let parseFileResults, _ = getParseAndCheckResults source
+ let res = parseFileResults.TryRangeOfRefCellDereferenceContainingPos (mkPos 3 10)
+ match res with
+ | Some res ->
+ res
+ |> tups
+ |> fst
+ |> shouldEqual (3, 8)
+ | None ->
+ Assert.Fail("No deref operator found in source.")
\ No newline at end of file
diff --git a/vsintegration/src/FSharp.Editor/CodeFix/ChangeRefCellDerefToNotExpression.fs b/vsintegration/src/FSharp.Editor/CodeFix/ChangeRefCellDerefToNotExpression.fs
new file mode 100644
index 00000000000..0d7b80e9d96
--- /dev/null
+++ b/vsintegration/src/FSharp.Editor/CodeFix/ChangeRefCellDerefToNotExpression.fs
@@ -0,0 +1,52 @@
+// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information.
+
+namespace Microsoft.VisualStudio.FSharp.Editor
+
+open System.Composition
+open System.Threading.Tasks
+
+open Microsoft.CodeAnalysis.Text
+open Microsoft.CodeAnalysis.CodeFixes
+
+[]
+type internal FSharpChangeRefCellDerefToNotExpressionCodeFixProvider
+ []
+ (
+ checkerProvider: FSharpCheckerProvider,
+ projectInfoManager: FSharpProjectOptionsManager
+ ) =
+ inherit CodeFixProvider()
+
+ static let userOpName = "FSharpChangeRefCellDerefToNotExpressionCodeFix"
+ let fixableDiagnosticIds = set ["FS0001"]
+
+ override __.FixableDiagnosticIds = Seq.toImmutableArray fixableDiagnosticIds
+
+ override this.RegisterCodeFixesAsync context : Task =
+ 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) |> liftAsync
+
+ let errorRange = RoslynHelpers.TextSpanToFSharpRange(document.FilePath, context.Span, sourceText)
+ let! derefRange = parseResults.TryRangeOfRefCellDereferenceContainingPos errorRange.Start
+ let! derefSpan = RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, derefRange)
+
+ let title = SR.UseNotForNegation()
+
+ let diagnostics =
+ context.Diagnostics
+ |> Seq.filter (fun x -> fixableDiagnosticIds |> Set.contains x.Id)
+ |> Seq.toImmutableArray
+
+ let codeFix =
+ CodeFixHelpers.createTextChangeCodeFix(
+ title,
+ context,
+ (fun () -> asyncMaybe.Return [| TextChange(derefSpan, "not ") |]))
+
+ context.RegisterCodeFix(codeFix, diagnostics)
+ }
+ |> Async.Ignore
+ |> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken)
\ No newline at end of file
diff --git a/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj b/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj
index 1091a3d4baf..4bbed460c4e 100644
--- a/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj
+++ b/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj
@@ -89,6 +89,7 @@
+
diff --git a/vsintegration/src/FSharp.Editor/FSharp.Editor.resx b/vsintegration/src/FSharp.Editor/FSharp.Editor.resx
index 802675e062e..94f2d6c4c58 100644
--- a/vsintegration/src/FSharp.Editor/FSharp.Editor.resx
+++ b/vsintegration/src/FSharp.Editor/FSharp.Editor.resx
@@ -1,4 +1,4 @@
-
+