diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
index 5cf08aea407..44cf398c407 100644
--- a/.devcontainer/devcontainer.json
+++ b/.devcontainer/devcontainer.json
@@ -1,7 +1,7 @@
// For format details, see https://aka.ms/vscode-remote/devcontainer.json or this file's README at:
{
"name": "F#",
- "image": "mcr.microsoft.com/dotnet/sdk:8.0.100-rc.1",
+ "image": "mcr.microsoft.com/dotnet/sdk:8.0",
"features": {
"ghcr.io/devcontainers/features/common-utils:2": {},
"ghcr.io/devcontainers/features/git:1": {},
diff --git a/Directory.Build.props b/Directory.Build.props
index 879bd89410f..10c53909035 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -16,6 +16,10 @@
true
+
+ true
+
+
true
diff --git a/Directory.Build.targets b/Directory.Build.targets
index 5952e0ed670..079e0e68fd4 100644
--- a/Directory.Build.targets
+++ b/Directory.Build.targets
@@ -13,7 +13,4 @@
-
-
-
diff --git a/FSharp.Benchmarks.sln b/FSharp.Benchmarks.sln
index 04af0d6830d..2ace22c1515 100644
--- a/FSharp.Benchmarks.sln
+++ b/FSharp.Benchmarks.sln
@@ -24,8 +24,6 @@ Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FSharp.Compiler.Benchmarks"
EndProject
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FCSSourceFiles", "tests\benchmarks\FCSBenchmarks\FCSSourceFiles\FCSSourceFiles.fsproj", "{0E2A7B27-3AD3-4C1D-BA0D-008A1200946F}"
EndProject
-Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Fsharp.ProfilingStartpointProject", "tests\benchmarks\Fsharp.ProfilingStartpointProject\Fsharp.ProfilingStartpointProject.fsproj", "{9F27346B-2FC6-4FD5-A932-4E80F331E6D6}"
-EndProject
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FSharp.Test.Utilities", "tests\FSharp.Test.Utilities\FSharp.Test.Utilities.fsproj", "{0B149238-0912-493E-8877-F831AE01B942}"
EndProject
Global
@@ -105,12 +103,6 @@ Global
{0E2A7B27-3AD3-4C1D-BA0D-008A1200946F}.Release|Any CPU.Build.0 = Release|Any CPU
{0E2A7B27-3AD3-4C1D-BA0D-008A1200946F}.ReleaseCompressed|Any CPU.ActiveCfg = Debug|Any CPU
{0E2A7B27-3AD3-4C1D-BA0D-008A1200946F}.Proto|Any CPU.ActiveCfg = Debug|Any CPU
- {9F27346B-2FC6-4FD5-A932-4E80F331E6D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {9F27346B-2FC6-4FD5-A932-4E80F331E6D6}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {9F27346B-2FC6-4FD5-A932-4E80F331E6D6}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {9F27346B-2FC6-4FD5-A932-4E80F331E6D6}.Release|Any CPU.Build.0 = Release|Any CPU
- {9F27346B-2FC6-4FD5-A932-4E80F331E6D6}.ReleaseCompressed|Any CPU.ActiveCfg = Debug|Any CPU
- {9F27346B-2FC6-4FD5-A932-4E80F331E6D6}.Proto|Any CPU.ActiveCfg = Debug|Any CPU
{0B149238-0912-493E-8877-F831AE01B942}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0B149238-0912-493E-8877-F831AE01B942}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0B149238-0912-493E-8877-F831AE01B942}.Release|Any CPU.ActiveCfg = Release|Any CPU
diff --git a/VisualFSharp.sln b/VisualFSharp.sln
index b7b9e82b5dc..485d887f3ce 100644
--- a/VisualFSharp.sln
+++ b/VisualFSharp.sln
@@ -189,8 +189,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "FCSBenchmarks", "FCSBenchma
tests\benchmarks\FCSBenchmarks\SmokeTestAllBenchmarks.ps1 = tests\benchmarks\FCSBenchmarks\SmokeTestAllBenchmarks.ps1
EndProjectSection
EndProject
-Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "Fsharp.ProfilingStartpointProject", "tests\benchmarks\Fsharp.ProfilingStartpointProject\Fsharp.ProfilingStartpointProject.fsproj", "{FE23BB65-276A-4E41-8CC7-F7752241DEBA}"
-EndProject
Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "FSharp.Editor.Tests", "vsintegration\tests\FSharp.Editor.Tests\FSharp.Editor.Tests.fsproj", "{CBC96CC7-65AB-46EA-A82E-F6A788DABF80}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FSharp.Editor.IntegrationTests", "vsintegration\tests\FSharp.Editor.IntegrationTests\FSharp.Editor.IntegrationTests.csproj", "{E31F9B59-FCF1-4D04-8762-C7BB60285A7B}"
@@ -997,18 +995,6 @@ Global
{583182E1-3484-4A8F-AC06-7C0D232C0CA4}.Release|Any CPU.Build.0 = Release|Any CPU
{583182E1-3484-4A8F-AC06-7C0D232C0CA4}.Release|x86.ActiveCfg = Release|Any CPU
{583182E1-3484-4A8F-AC06-7C0D232C0CA4}.Release|x86.Build.0 = Release|Any CPU
- {FE23BB65-276A-4E41-8CC7-F7752241DEBA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {FE23BB65-276A-4E41-8CC7-F7752241DEBA}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {FE23BB65-276A-4E41-8CC7-F7752241DEBA}.Debug|x86.ActiveCfg = Debug|Any CPU
- {FE23BB65-276A-4E41-8CC7-F7752241DEBA}.Debug|x86.Build.0 = Debug|Any CPU
- {FE23BB65-276A-4E41-8CC7-F7752241DEBA}.Proto|Any CPU.ActiveCfg = Debug|Any CPU
- {FE23BB65-276A-4E41-8CC7-F7752241DEBA}.Proto|Any CPU.Build.0 = Debug|Any CPU
- {FE23BB65-276A-4E41-8CC7-F7752241DEBA}.Proto|x86.ActiveCfg = Debug|Any CPU
- {FE23BB65-276A-4E41-8CC7-F7752241DEBA}.Proto|x86.Build.0 = Debug|Any CPU
- {FE23BB65-276A-4E41-8CC7-F7752241DEBA}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {FE23BB65-276A-4E41-8CC7-F7752241DEBA}.Release|Any CPU.Build.0 = Release|Any CPU
- {FE23BB65-276A-4E41-8CC7-F7752241DEBA}.Release|x86.ActiveCfg = Release|Any CPU
- {FE23BB65-276A-4E41-8CC7-F7752241DEBA}.Release|x86.Build.0 = Release|Any CPU
{CBC96CC7-65AB-46EA-A82E-F6A788DABF80}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CBC96CC7-65AB-46EA-A82E-F6A788DABF80}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CBC96CC7-65AB-46EA-A82E-F6A788DABF80}.Debug|x86.ActiveCfg = Debug|Any CPU
@@ -1111,7 +1097,6 @@ Global
{EB015235-1E07-4CDA-9CC6-3FBCC27910D1} = {B8DDA694-7939-42E3-95E5-265C2217C142}
{583182E1-3484-4A8F-AC06-7C0D232C0CA4} = {39CDF34B-FB23-49AE-AB27-0975DA379BB5}
{39CDF34B-FB23-49AE-AB27-0975DA379BB5} = {DFB6ADD7-3149-43D9-AFA0-FC4A818B472B}
- {FE23BB65-276A-4E41-8CC7-F7752241DEBA} = {39CDF34B-FB23-49AE-AB27-0975DA379BB5}
{CBC96CC7-65AB-46EA-A82E-F6A788DABF80} = {F7876C9B-FB6A-4EFB-B058-D6967DB75FB2}
{E31F9B59-FCF1-4D04-8762-C7BB60285A7B} = {F7876C9B-FB6A-4EFB-B058-D6967DB75FB2}
EndGlobalSection
diff --git a/azure-pipelines.yml b/azure-pipelines.yml
index 2434a430a22..6b11aca6687 100644
--- a/azure-pipelines.yml
+++ b/azure-pipelines.yml
@@ -713,6 +713,33 @@ stages:
continueOnError: true
condition: always()
+ # Build benchmarks
+ - job: Plain_Build_Benchmarks
+ pool:
+ name: $(DncEngPublicBuildPool)
+ demands: ImageOverride -equals $(WindowsMachineQueueName)
+ variables:
+ - name: _BuildConfig
+ value: Debug
+ steps:
+ - checkout: self
+ clean: true
+ - script: dotnet --list-sdks
+ displayName: Report dotnet SDK versions
+ - task: UseDotNet@2
+ displayName: install SDK
+ inputs:
+ packageType: sdk
+ useGlobalJson: true
+ includePreviewVersions: true
+ workingDirectory: $(Build.SourcesDirectory)
+ installationPath: $(Agent.ToolsDirectory)/dotnet
+ - script: dotnet build .\FSharp.Benchmarks.sln /bl:\"artifacts/log/$(_BuildConfig)/BenchmarkBuild.binlog\"
+ workingDirectory: $(Build.SourcesDirectory)
+ displayName: Regular rebuild of FSharp.Benchmarks.sln
+ continueOnError: true
+ condition: always()
+
# Test trimming on Windows
- job: Build_And_Test_Trimming_Windows
pool:
diff --git a/buildtools/AssemblyCheck/SkipVerifyEmbeddedPdb.txt b/buildtools/AssemblyCheck/SkipVerifyEmbeddedPdb.txt
index 9ea06757477..f823017f4a1 100644
--- a/buildtools/AssemblyCheck/SkipVerifyEmbeddedPdb.txt
+++ b/buildtools/AssemblyCheck/SkipVerifyEmbeddedPdb.txt
@@ -1,6 +1,5 @@
FSharp.Build.UnitTests.dll
FSharp.Compiler.Benchmarks.dll
-Fsharp.ProfilingStartpointProject.dll
FSharp.Compiler.ComponentTests.dll
FSharp.Test.Utilities.dll
FSharp.Compiler.Private.Scripting.UnitTests.dll
diff --git a/docs/fcs/syntax-visitor.fsx b/docs/fcs/syntax-visitor.fsx
deleted file mode 100644
index f0ea0316cfb..00000000000
--- a/docs/fcs/syntax-visitor.fsx
+++ /dev/null
@@ -1,189 +0,0 @@
-(**
----
-title: Tutorial: SyntaxVisitorBase
-category: FSharp.Compiler.Service
-categoryindex: 300
-index: 301
----
-*)
-(*** hide ***)
-#I "../../artifacts/bin/FSharp.Compiler.Service/Debug/netstandard2.0"
-(**
-Compiler Services: Using the SyntaxVisitorBase
-=========================================
-
-Syntax tree traversal is a common topic when interacting with the `FSharp.Compiler.Service`.
-As established in [Tutorial: Expressions](./untypedtree.html#Walking-over-the-AST), the [ParsedInput](../reference/fsharp-compiler-syntax-parsedinput.html) can be traversed by a set of recursive functions.
-It can be tedious to always construct these functions from scratch.
-
-As an alternative, a [SyntaxVisitorBase](../reference/fsharp-compiler-syntax-syntaxvisitorbase-1.html) can be used to traverse the syntax tree.
-Consider, the following code sample:
-*)
-
-let codeSample = """
-module Lib
-
-let myFunction paramOne paramTwo =
- ()
-"""
-
-(**
-Imagine we wish to grab the `myFunction` name from the `headPat` in the [SynBinding](../reference/fsharp-compiler-syntax-synbinding.html).
-Let's introduce a helper function to construct the AST:
-*)
-
-#r "FSharp.Compiler.Service.dll"
-open FSharp.Compiler.CodeAnalysis
-open FSharp.Compiler.Text
-open FSharp.Compiler.Syntax
-
-let checker = FSharpChecker.Create()
-
-/// Helper to construct an ParsedInput from a code snippet.
-let mkTree codeSample =
- let parseFileResults =
- checker.ParseFile(
- "FileName.fs",
- SourceText.ofString codeSample,
- { FSharpParsingOptions.Default with SourceFiles = [| "FileName.fs" |] }
- )
- |> Async.RunSynchronously
-
- parseFileResults.ParseTree
-
-(**
-And create a visitor to traverse the tree:
-*)
-
-let visitor =
- { new SyntaxVisitorBase() with
- override this.VisitPat(path, defaultTraverse, synPat) =
- // First check if the pattern is what we are looking for.
- match synPat with
- | SynPat.LongIdent(longDotId = SynLongIdent(id = [ ident ])) ->
- // Next we can check if the current path of visited nodes, matches our expectations.
- // The path will contain all the ancestors of the current node.
- match path with
- // The parent node of `synPat` should be a `SynBinding`.
- | SyntaxNode.SynBinding _ :: _ ->
- // We return a `Some` option to indicate we found what we are looking for.
- Some ident.idText
- // If the parent is something else, we can skip it here.
- | _ -> None
- | _ -> None }
-
-let result = SyntaxTraversal.Traverse(Position.pos0, mkTree codeSample, visitor) // Some "myFunction"
-
-(**
-Instead of traversing manually from `ParsedInput` to `SynModuleOrNamespace` to `SynModuleDecl.Let` to `SynBinding` to `SynPat`, we leverage the default navigation that happens in `SyntaxTraversal.Traverse`.
-A `SyntaxVisitorBase` will shortcut all other code paths once a single `VisitXYZ` override has found anything.
-
-Our code sample of course only had one let binding and thus we didn't need to specify any further logic whether to differentiate between multiple bindings.
-Let's consider a second example where we know the user's cursor inside an IDE is placed after `c` and we are interested in the body expression of the let binding.
-*)
-
-let secondCodeSample = """
-module X
-
-let a = 0
-let b = 1
-let c = 2
-"""
-
-let secondVisitor =
- { new SyntaxVisitorBase() with
- override this.VisitBinding(path, defaultTraverse, binding) =
- match binding with
- | SynBinding(expr = e) -> Some e }
-
-let cursorPos = Position.mkPos 6 5
-
-let secondResult =
- SyntaxTraversal.Traverse(cursorPos, mkTree secondCodeSample, secondVisitor) // Some (Const (Int32 2, (6,8--6,9)))
-
-(**
-Due to our passed cursor position, we did not need to write any code to exclude the expressions of the other let bindings.
-`SyntaxTraversal.Traverse` will check whether the current position is inside any syntax node before drilling deeper.
-
-Lastly, some `VisitXYZ` overrides can contain a defaultTraverse. This helper allows you to continue the default traversal when you currently hit a node that is not of interest.
-Consider `1 + 2 + 3 + 4`, this will be reflected in a nested infix application expression.
-If the cursor is at the end of the entire expression, we can grab the value of `4` using the following visitor:
-*)
-
-let thirdCodeSample = "let sum = 1 + 2 + 3 + 4"
-
-(*
-AST will look like:
-
-Let
- (false,
- [SynBinding
- (None, Normal, false, false, [],
- PreXmlDoc ((1,0), Fantomas.FCS.Xml.XmlDocCollector),
- SynValData
- (None, SynValInfo ([], SynArgInfo ([], false, None)), None,
- None),
- Named (SynIdent (sum, None), false, None, (1,4--1,7)), None,
- App
- (NonAtomic, false,
- App
- (NonAtomic, true,
- LongIdent
- (false,
- SynLongIdent
- ([op_Addition], [], [Some (OriginalNotation "+")]),
- None, (1,20--1,21)),
- App
- (NonAtomic, false,
- App
- (NonAtomic, true,
- LongIdent
- (false,
- SynLongIdent
- ([op_Addition], [],
- [Some (OriginalNotation "+")]), None,
- (1,16--1,17)),
- App
- (NonAtomic, false,
- App
- (NonAtomic, true,
- LongIdent
- (false,
- SynLongIdent
- ([op_Addition], [],
- [Some (OriginalNotation "+")]), None,
- (1,12--1,13)),
- Const (Int32 1, (1,10--1,11)), (1,10--1,13)),
- Const (Int32 2, (1,14--1,15)), (1,10--1,15)),
- (1,10--1,17)), Const (Int32 3, (1,18--1,19)),
- (1,10--1,19)), (1,10--1,21)),
- Const (Int32 4, (1,22--1,23)), (1,10--1,23)), (1,4--1,7),
- Yes (1,0--1,23), { LeadingKeyword = Let (1,0--1,3)
- InlineKeyword = None
- EqualsRange = Some (1,8--1,9) })
-*)
-
-let thirdCursorPos = Position.mkPos 1 22
-
-let thirdVisitor =
- { new SyntaxVisitorBase() with
- override this.VisitExpr(path, traverseSynExpr, defaultTraverse, synExpr) =
- match synExpr with
- | SynExpr.Const (constant = SynConst.Int32 v) -> Some v
- // We do want to continue to traverse when nodes like `SynExpr.App` are found.
- | otherExpr -> defaultTraverse otherExpr }
-
-let thirdResult =
- SyntaxTraversal.Traverse(cursorPos, mkTree thirdCodeSample, thirdVisitor) // Some 4
-
-(**
-`defaultTraverse` is especially useful when you do not know upfront what syntax tree you will be walking.
-This is a common case when dealing with IDE tooling. You won't know what actual code the end-user is currently processing.
-
-**Note: SyntaxVisitorBase is designed to find a single value inside a tree!**
-This is not an ideal solution when you are interested in all nodes of certain shape.
-It will always verify if the given cursor position is still matching the range of the node.
-As a fallback the first branch will be explored when you pass `Position.pos0`.
-By design, it is meant to find a single result.
-
-*)
diff --git a/docs/fcs/untypedtree-apis.fsx b/docs/fcs/untypedtree-apis.fsx
new file mode 100644
index 00000000000..c713ad8acff
--- /dev/null
+++ b/docs/fcs/untypedtree-apis.fsx
@@ -0,0 +1,553 @@
+(**
+---
+title: Tutorial: AST APIs
+category: FSharp.Compiler.Service
+categoryindex: 300
+index: 301
+---
+*)
+(*** hide ***)
+#I "../../artifacts/bin/FSharp.Compiler.Service/Debug/netstandard2.0"
+(**
+Compiler Services: APIs for the untyped AST
+=========================================
+
+## The ParsedInput module
+
+As established in [Tutorial: Expressions](./untypedtree.html#Walking-over-the-AST), the AST held in a [`ParsedInput`](../reference/fsharp-compiler-syntax-parsedinput.html) value
+can be traversed by a set of recursive functions. It can be tedious and error-prone to write these functions from scratch every time, though,
+so the [`ParsedInput` module](../reference/fsharp-compiler-syntax-parsedinputmodule.html)
+exposes a number of functions to make common operations easier.
+
+For example:
+
+- [`ParsedInput.exists`](../reference/fsharp-compiler-syntax-parsedinputmodule.html#exists)
+ - May be used by tooling to determine whether the user's cursor is in a certain context, e.g., to determine whether to offer a certain tooling action.
+- [`ParsedInput.fold`](../reference/fsharp-compiler-syntax-parsedinputmodule.html#fold)
+ - May be used when writing analyzers to collect diagnostic information for an entire source file.
+- [`ParsedInput.foldWhile`](../reference/fsharp-compiler-syntax-parsedinputmodule.html#foldWhile)
+ - Like `fold` but supports stopping traversal early.
+- [`ParsedInput.tryNode`](../reference/fsharp-compiler-syntax-parsedinputmodule.html#tryNode)
+ - May be used by tooling to get the last (deepest) node under the user's cursor.
+- [`ParsedInput.tryPick`](../reference/fsharp-compiler-syntax-parsedinputmodule.html#tryPick)
+ - May be used by tooling to find the first (shallowest) matching node near the user's cursor.
+- [`ParsedInput.tryPickLast`](../reference/fsharp-compiler-syntax-parsedinputmodule.html#tryPickLast)
+ - May be used by tooling to find the last (deepest) matching node near the user's cursor.
+
+## SyntaxVisitorBase & SyntaxTraversal.Traverse
+
+While the `ParsedInput` module functions are usually the simplest way to meet most needs,
+there is also a [`SyntaxVisitorBase`](../reference/fsharp-compiler-syntax-syntaxvisitorbase-1.html)-based API that can
+provide somewhat more fine-grained control over syntax traversal for a subset of use-cases at the expense of a bit more
+ceremony and complexity.
+
+## Examples
+
+Let's start by introducing a helper function for constructing an AST from source code so we can run through some real examples:
+*)
+
+#r "FSharp.Compiler.Service.dll"
+open FSharp.Compiler.CodeAnalysis
+open FSharp.Compiler.Text
+open FSharp.Compiler.Syntax
+
+let checker = FSharpChecker.Create()
+
+/// A helper for constructing a `ParsedInput` from a code snippet.
+let mkTree codeSample =
+ let parseFileResults =
+ checker.ParseFile(
+ "FileName.fs",
+ SourceText.ofString codeSample,
+ { FSharpParsingOptions.Default with SourceFiles = [| "FileName.fs" |] }
+ )
+ |> Async.RunSynchronously
+
+ parseFileResults.ParseTree
+
+(**
+### ParsedInput.exists
+
+Now consider the following code sample:
+*)
+
+let brokenTypeDefn = """
+module Lib
+
+// Whoops, we forgot the equals sign.
+type T { A: int; B: int }
+"""
+
+(**
+Let's say we have a code fix for adding an equals sign to a type definition that's missing one—like the one above.
+We want to offer the fix when the user's cursor is inside of—or just after—the broken type definition.
+
+We can determine this by using `ParsedInput.exists` and passing in the position of the user's cursor:
+*)
+
+// type T { A: int; B: int }
+// ···········↑
+let posInMiddleOfTypeDefn = Position.mkPos 5 12
+
+(**
+Given that cursor position, all we need to do is find a `SynTypeDefn` node:
+*)
+
+let isPosInTypeDefn = // true.
+ (posInMiddleOfTypeDefn, mkTree brokenTypeDefn)
+ ||> ParsedInput.exists (fun _path node ->
+ match node with
+ | SyntaxNode.SynTypeDefn _ -> true
+ | _ -> false)
+
+(**
+If the position passed into `ParsedInput.exists` is not contained in any node in the given AST,
+but rather is below or to the right of all nodes, `ParsedInput.exists` will fall back to exploring the nearest branch above
+and/or to the left. This is useful because the user's cursor may lie beyond the range of all nodes.
+*)
+
+// type T { A: int; B: int }
+// ··························↑
+let posAfterTypeDefn = Position.mkPos 5 28
+
+(**
+Our function still returns `true` if the cursor is past the end of the type definition node itself:
+*)
+
+let isPosInTypeDefn' = // Still true.
+ (posAfterTypeDefn, mkTree brokenTypeDefn)
+ ||> ParsedInput.exists (fun _path node ->
+ match node with
+ | SyntaxNode.SynTypeDefn _ -> true
+ | _ -> false)
+
+(**
+### ParsedInput.fold
+
+`ParsedInput.fold` can be useful when writing an analyzer to collect diagnostics from entire input files.
+*)
+
+(*** hide ***)
+let getLineStr (line: int) : string = failwith "Nope."
+
+(**
+Take this code that has unnecessary parentheses in both patterns and expressions:
+*)
+
+let unnecessaryParentheses = """
+let (x) = (id (3))
+"""
+
+(**
+We can gather the ranges of all unnecessary parentheses like this:
+*)
+
+open System.Collections.Generic
+
+module HashSet =
+ let add item (set: HashSet<_>) =
+ ignore (set.Add item)
+ set
+
+let unnecessaryParenthesesRanges =
+ (HashSet Range.comparer, mkTree unnecessaryParentheses) ||> ParsedInput.fold (fun ranges path node ->
+ match node with
+ | SyntaxNode.SynExpr(SynExpr.Paren(expr = inner; rightParenRange = Some _; range = range)) when
+ not (SynExpr.shouldBeParenthesizedInContext getLineStr path inner)
+ ->
+ ranges |> HashSet.add range
+
+ | SyntaxNode.SynPat(SynPat.Paren(inner, range)) when
+ not (SynPat.shouldBeParenthesizedInContext path inner)
+ ->
+ ranges |> HashSet.add range
+
+ | _ ->
+ ranges)
+
+(**
+### ParsedInput.tryNode
+
+Sometimes, we might just want to get whatever node is directly at a given position—for example, if the user's
+cursor is on an argument of a function being applied, we can find the node representing the argument and use its path
+to backtrack and find the function's name.
+*)
+
+let functionApplication = """
+f x y
+"""
+
+(**
+If we have our cursor on `y`:
+*)
+
+// f x y
+// ·····↑
+let posOnY = Position.mkPos 2 5
+
+(**
+The syntax node representing the function `f` technically contains the cursor's position,
+but `ParsedInput.tryNode` will keep diving until it finds the _deepest_ node containing the position.
+
+We can thus get the node representing `y` and its ancestors (the `path`) like this:
+*)
+
+let yAndPath = // Some (SynExpr (Ident y), [SynExpr (App …); …])
+ mkTree functionApplication
+ |> ParsedInput.tryNode posOnY
+
+(**
+Note that, unlike `ParsedInput.exists`, `ParsedInput.tryPick`, and `ParsedInput.tryPickLast`,
+`ParsedInput.tryNode` does _not_ fall back to the nearest branch above or to the left.
+*)
+
+// f x y
+// ······↑
+let posAfterY = Position.mkPos 2 8
+
+(**
+If we take the same code snippet but pass in a position after `y`,
+we get no node:
+*)
+
+let nope = // None.
+ mkTree functionApplication
+ |> ParsedInput.tryNode posAfterY
+
+(**
+### ParsedInput.tryPick
+
+Now imagine that we have a code fix for converting a record construction expression into an anonymous record construction
+expression when there is no record type in scope whose fields match.
+*)
+
+let recordExpr = """
+let r = { A = 1; B = 2 }
+"""
+
+(**
+We can offer this fix when the user's cursor is inside of a record expression by
+using `ParsedInput.tryPick` to return the surrounding record expression's range, if any.
+*)
+
+// let r = { A = 1; B = 2 }
+// ······················↑
+let posInRecordExpr = Position.mkPos 2 25
+
+(**
+Here, even though `ParsedInput.tryPick` will try to cleave to the given position by default,
+we want to verify that the record expression node that we've come across actually contains the position,
+since, like `ParsedInput.exists`, `ParsedInput.tryPick` will also fall back to the nearest branch above and/or
+to the left if no node actually contains the position. In this case, we don't want to offer the code fix
+if the user's cursor isn't actually inside of the record expression.
+*)
+
+let recordExprRange = // Some (2,8--2,24).
+ (posInRecordExpr, mkTree recordExpr)
+ ||> ParsedInput.tryPick (fun _path node ->
+ match node with
+ | SyntaxNode.SynExpr(SynExpr.Record(range = range)) when
+ Range.rangeContainsPos range posInRecordExpr
+ -> Some range
+ | _ -> None)
+
+(**
+We might also sometimes want to make use of the `path` parameter. Take this simple function definition:
+*)
+
+let myFunction = """
+module Lib
+
+let myFunction paramOne paramTwo =
+ ()
+"""
+
+(**
+Imagine we want to grab the `myFunction` name from the `headPat` in the [`SynBinding`](../reference/fsharp-compiler-syntax-synbinding.html).
+
+We can write a function to match the node we're looking for—and _not_ match anything we're _not_ looking for (like the argument patterns)—by taking its path into account:
+*)
+
+let myFunctionId = // Some "myFunction".
+ (Position.pos0, mkTree myFunction)
+ ||> ParsedInput.tryPick (fun path node ->
+ // Match on the node and the path (the node's ancestors) to see whether:
+ // 1. The node is a pattern.
+ // 2. The pattern is a long identifier pattern.
+ // 3. The pattern's parent node (the head of the path) is a binding.
+ match node, path with
+ | SyntaxNode.SynPat(SynPat.LongIdent(longDotId = SynLongIdent(id = [ ident ]))),
+ SyntaxNode.SynBinding _ :: _ ->
+ // We have found what we're looking for.
+ Some ident.idText
+ | _ ->
+ // If the node or its context don't match,
+ // we continue.
+ None)
+
+(**
+Instead of traversing manually from `ParsedInput` to `SynModuleOrNamespace` to `SynModuleDecl.Let` to `SynBinding` to `SynPat`, we leverage the default navigation that happens in `ParsedInput.tryPick`.
+`ParsedInput.tryPick` will short-circuit once we have indicated that we have found what we're looking for by returning `Some value`.
+
+Our code sample of course only had one let-binding and thus we didn't need to specify any further logic to differentiate between bindings.
+
+Let's consider a second example involving multiple let-bindings:
+*)
+
+let multipleLetsInModule = """
+module X
+
+let a = 0
+let b = 1
+let c = 2
+"""
+
+(**
+In this case, we know the user's cursor inside an IDE is placed after `c`, and we are interested in the body expression of the _last_ let-binding.
+*)
+
+// …
+// let c = 2
+// ·····↑
+let posInLastLet = Position.mkPos 6 5
+
+(**
+Thanks to the cursor position we passed in, we do not need to write any code to exclude the expressions of the sibling let-bindings.
+`ParsedInput.tryPick` will check whether the current position is inside any given syntax node before drilling deeper.
+*)
+
+let bodyOfLetContainingPos = // Some (Const (Int32 2, (6,8--6,9))).
+ (posInLastLet, mkTree multipleLetsInModule)
+ ||> ParsedInput.tryPick (fun _path node ->
+ match node with
+ | SyntaxNode.SynBinding(SynBinding(expr = e)) -> Some e
+ | _ -> None)
+
+(**
+As noted above, `ParsedInput.tryPick` will short-circuit at the first matching node.
+`ParsedInput.tryPickLast` can be used to get the _last_ matching node that contains a given position.
+
+Take this example of multiple nested modules:
+*)
+
+let nestedModules = """
+module M
+
+module N =
+ module O =
+ module P = begin end
+"""
+
+(**
+By using `ParsedInput.tryPick`, we'll get the name of the outermost nested module even if we pass in a position inside the innermost,
+since the innermost is contained within the outermost.
+
+This position is inside module `P`, which is nested inside of module `O`, which is nested inside of module `N`,
+which is nested inside of top-level module `M`:
+*)
+
+// module M
+//
+// module N =
+// module O =
+// module P = begin end
+// ···························↑
+let posInsideOfInnermostNestedModule = Position.mkPos 6 28
+
+(**
+`ParsedInput.tryPick` short-circuits on the first match, and since module `N` is the first
+nested module whose range contains position (6, 28), that's the result we get.
+*)
+
+let outermostNestedModule = // Some ["N"].
+ (posInsideOfInnermostNestedModule, mkTree nestedModules)
+ ||> ParsedInput.tryPick (fun _path node ->
+ match node with
+ | SyntaxNode.SynModule(SynModuleDecl.NestedModule(moduleInfo = SynComponentInfo(longId = longId))) ->
+ Some [for ident in longId -> ident.idText]
+ | _ -> None)
+
+(**
+### ParsedInput.tryPickLast
+
+If however we use the same code snippet and pass the same position into `ParsedInput.tryPickLast`,
+we can get the name of the _last_ (deepest or innermost) matching node:
+*)
+
+let innermostNestedModule = // Some ["P"].
+ (posInsideOfInnermostNestedModule, mkTree nestedModules)
+ ||> ParsedInput.tryPickLast (fun _path node ->
+ match node with
+ | SyntaxNode.SynModule(SynModuleDecl.NestedModule(moduleInfo = SynComponentInfo(longId = longId))) ->
+ Some [for ident in longId -> ident.idText]
+ | _ -> None)
+
+(**
+If we want the next-to-innermost nested module, we can do likewise but make use of the `path` parameter:
+*)
+
+let nextToInnermostNestedModule = // Some ["O"].
+ (posInsideOfInnermostNestedModule, mkTree nestedModules)
+ ||> ParsedInput.tryPickLast (fun path node ->
+ match node, path with
+ | SyntaxNode.SynModule(SynModuleDecl.NestedModule _),
+ SyntaxNode.SynModule(SynModuleDecl.NestedModule(moduleInfo = SynComponentInfo(longId = longId))) :: _ ->
+ Some [for ident in longId -> ident.idText]
+ | _ -> None)
+
+(**
+### SyntaxTraversal.Traverse
+
+Consider again the following code sample:
+*)
+
+let codeSample = """
+module Lib
+
+let myFunction paramOne paramTwo =
+ ()
+"""
+
+(**
+Imagine we wish to grab the `myFunction` name from the `headPat` in the [SynBinding](../reference/fsharp-compiler-syntax-synbinding.html).
+
+We can create a visitor to traverse the tree and find the function name:
+*)
+
+let visitor =
+ { new SyntaxVisitorBase() with
+ override this.VisitPat(path, defaultTraverse, synPat) =
+ // First check if the pattern is what we are looking for.
+ match synPat with
+ | SynPat.LongIdent(longDotId = SynLongIdent(id = [ ident ])) ->
+ // Next we can check if the current path of visited nodes, matches our expectations.
+ // The path will contain all the ancestors of the current node.
+ match path with
+ // The parent node of `synPat` should be a `SynBinding`.
+ | SyntaxNode.SynBinding _ :: _ ->
+ // We return a `Some` option to indicate we found what we are looking for.
+ Some ident.idText
+ // If the parent is something else, we can skip it here.
+ | _ -> None
+ | _ -> None }
+
+let result = SyntaxTraversal.Traverse(Position.pos0, mkTree codeSample, visitor) // Some "myFunction"
+
+(**
+Instead of traversing manually from `ParsedInput` to `SynModuleOrNamespace` to `SynModuleDecl.Let` to `SynBinding` to `SynPat`, we leverage the default navigation that happens in `SyntaxTraversal.Traverse`.
+A `SyntaxVisitorBase` will shortcut all other code paths once a single `VisitXYZ` override has found anything.
+
+Our code sample of course only had one let binding and thus we didn't need to specify any further logic whether to differentiate between multiple bindings.
+
+### SyntaxTraversal.Traverse: using position
+
+Let's now consider a second example where we know the user's cursor inside an IDE is placed after `c` and we are interested in the body expression of the let binding.
+*)
+
+let secondCodeSample = """
+module X
+
+let a = 0
+let b = 1
+let c = 2
+"""
+
+let secondVisitor =
+ { new SyntaxVisitorBase() with
+ override this.VisitBinding(path, defaultTraverse, binding) =
+ match binding with
+ | SynBinding(expr = e) -> Some e }
+
+let cursorPos = Position.mkPos 6 5
+
+let secondResult =
+ SyntaxTraversal.Traverse(cursorPos, mkTree secondCodeSample, secondVisitor) // Some (Const (Int32 2, (6,8--6,9)))
+
+(**
+Due to our passed cursor position, we did not need to write any code to exclude the expressions of the other let bindings.
+`SyntaxTraversal.Traverse` will check whether the current position is inside any syntax node before drilling deeper.
+
+### SyntaxTraversal.Traverse: using defaultTraverse
+
+Lastly, some `VisitXYZ` overrides can contain a defaultTraverse. This helper allows you to continue the default traversal when you currently hit a node that is not of interest.
+Consider `1 + 2 + 3 + 4`, this will be reflected in a nested infix application expression.
+If the cursor is at the end of the entire expression, we can grab the value of `4` using the following visitor:
+*)
+
+let thirdCodeSample = "let sum = 1 + 2 + 3 + 4"
+
+(*
+AST will look like:
+
+Let
+ (false,
+ [SynBinding
+ (None, Normal, false, false, [],
+ PreXmlDoc ((1,0), Fantomas.FCS.Xml.XmlDocCollector),
+ SynValData
+ (None, SynValInfo ([], SynArgInfo ([], false, None)), None,
+ None),
+ Named (SynIdent (sum, None), false, None, (1,4--1,7)), None,
+ App
+ (NonAtomic, false,
+ App
+ (NonAtomic, true,
+ LongIdent
+ (false,
+ SynLongIdent
+ ([op_Addition], [], [Some (OriginalNotation "+")]),
+ None, (1,20--1,21)),
+ App
+ (NonAtomic, false,
+ App
+ (NonAtomic, true,
+ LongIdent
+ (false,
+ SynLongIdent
+ ([op_Addition], [],
+ [Some (OriginalNotation "+")]), None,
+ (1,16--1,17)),
+ App
+ (NonAtomic, false,
+ App
+ (NonAtomic, true,
+ LongIdent
+ (false,
+ SynLongIdent
+ ([op_Addition], [],
+ [Some (OriginalNotation "+")]), None,
+ (1,12--1,13)),
+ Const (Int32 1, (1,10--1,11)), (1,10--1,13)),
+ Const (Int32 2, (1,14--1,15)), (1,10--1,15)),
+ (1,10--1,17)), Const (Int32 3, (1,18--1,19)),
+ (1,10--1,19)), (1,10--1,21)),
+ Const (Int32 4, (1,22--1,23)), (1,10--1,23)), (1,4--1,7),
+ Yes (1,0--1,23), { LeadingKeyword = Let (1,0--1,3)
+ InlineKeyword = None
+ EqualsRange = Some (1,8--1,9) })
+*)
+
+let thirdCursorPos = Position.mkPos 1 22
+
+let thirdVisitor =
+ { new SyntaxVisitorBase() with
+ override this.VisitExpr(path, traverseSynExpr, defaultTraverse, synExpr) =
+ match synExpr with
+ | SynExpr.Const (constant = SynConst.Int32 v) -> Some v
+ // We do want to continue to traverse when nodes like `SynExpr.App` are found.
+ | otherExpr -> defaultTraverse otherExpr }
+
+let thirdResult =
+ SyntaxTraversal.Traverse(cursorPos, mkTree thirdCodeSample, thirdVisitor) // Some 4
+
+(**
+`defaultTraverse` is especially useful when you do not know upfront what syntax tree you will be walking.
+This is a common case when dealing with IDE tooling. You won't know what actual code the end-user is currently processing.
+
+**Note: SyntaxVisitorBase is designed to find a single value inside a tree!**
+This is not an ideal solution when you are interested in all nodes of certain shape.
+It will always verify if the given cursor position is still matching the range of the node.
+As a fallback the first branch will be explored when you pass `Position.pos0`.
+By design, it is meant to find a single result.
+
+*)
diff --git a/docs/release-notes/.FSharp.Compiler.Service/8.0.200.md b/docs/release-notes/.FSharp.Compiler.Service/8.0.200.md
index 65f52c982fa..175d90b6302 100644
--- a/docs/release-notes/.FSharp.Compiler.Service/8.0.200.md
+++ b/docs/release-notes/.FSharp.Compiler.Service/8.0.200.md
@@ -1,6 +1,5 @@
### Fixed
-* Miscellaneous fixes to parentheses analysis. ([PR #16262](https://github.com/dotnet/fsharp/pull/16262), [PR #16391](https://github.com/dotnet/fsharp/pull/16391), [PR #16370](https://github.com/dotnet/fsharp/pull/16370), [PR #16395](https://github.com/dotnet/fsharp/pull/16395), [PR #16372](https://github.com/dotnet/fsharp/pull/16372))
* Correctly handle assembly imports with public key token of 0 length. ([Issue #16359](https://github.com/dotnet/fsharp/issues/16359), [PR #16363](https://github.com/dotnet/fsharp/pull/16363))
* Range of [SynField](../reference/fsharp-compiler-syntax-synfield.html) ([PR #16357](https://github.com/dotnet/fsharp/pull/16357))
* Limit a type to 65K methods, introduce a compile-time error if any class has over approx 64K methods in generated IL. ([Issue #16398](https://github.com/dotnet/fsharp/issues/16398), [#PR 16427](https://github.com/dotnet/fsharp/pull/16427))
@@ -15,7 +14,8 @@
* Parser recovers on unfinished record declarations. ([PR #16357](https://github.com/dotnet/fsharp/pull/16357))
* `MutableKeyword` to [SynFieldTrivia](../reference/fsharp-compiler-syntaxtrivia-synfieldtrivia.html) ([PR #16357](https://github.com/dotnet/fsharp/pull/16357))
* Added support for a new parameterless constructor for `CustomOperationAttribute`, which, when applied, will use method name as keyword for custom operation in computation expression builder. ([PR #16475](https://github.com/dotnet/fsharp/pull/16475), part of implementation for [fslang-suggestions/1250](https://github.com/fsharp/fslang-suggestions/issues/1250))
+* Compiler service API for getting ranges of unnecessary parentheses. ([PR #16079](https://github.com/dotnet/fsharp/pull/16079) et seq.)
### Changed
-* Speed up unused opens handling for empty results. ([PR #16502](https://github.com/dotnet/fsharp/pull/16502))
\ No newline at end of file
+* Speed up unused opens handling for empty results. ([PR #16502](https://github.com/dotnet/fsharp/pull/16502))
diff --git a/docs/release-notes/.FSharp.Compiler.Service/8.0.300.md b/docs/release-notes/.FSharp.Compiler.Service/8.0.300.md
index ac442a61549..8fa3c363b20 100644
--- a/docs/release-notes/.FSharp.Compiler.Service/8.0.300.md
+++ b/docs/release-notes/.FSharp.Compiler.Service/8.0.300.md
@@ -1,12 +1,20 @@
### Fixed
* Code generated files with > 64K methods and generated symbols crash when loaded. Use infered sequence points for debugging. ([Issue #16399](https://github.com/dotnet/fsharp/issues/16399), [#PR 16514](https://github.com/dotnet/fsharp/pull/16514))
+* `nameof Module` expressions and patterns are processed to link files in `--test:GraphBasedChecking`. ([PR #16550](https://github.com/dotnet/fsharp/pull/16550))
+* Graph Based Checking doesn't throw on invalid parsed input so it can be used for IDE scenarios ([PR #16575](https://github.com/dotnet/fsharp/pull/16575))
### Added
* Parser recovers on complex primary constructor patterns, better tree representation for primary constructor patterns. ([PR #16425](https://github.com/dotnet/fsharp/pull/16425))
+* Name resolution: keep type vars in subsequent checks ([PR #16456](https://github.com/dotnet/fsharp/pull/16456))
+* Higher-order-function-based API for working with the untyped abstract syntax tree. ([PR #16462](https://github.com/dotnet/fsharp/pull/16462))
### Changed
+* Autogenerated .Is* members for unions skipped for single-case unions. ([PR 16571](https://github.com/dotnet/fsharp/pull/16571))
* `implicitCtorSynPats` in `SynTypeDefnSimpleRepr.General` is now `SynPat option` instead of `SynSimplePats option`. ([PR #16425](https://github.com/dotnet/fsharp/pull/16425))
-* `SyntaxVisitorBase<'T>.VisitSimplePats` now takes `SynPat` instead of `SynSimplePat list`. ([PR #16425](https://github.com/dotnet/fsharp/pull/16425))
\ No newline at end of file
+* `SyntaxVisitorBase<'T>.VisitSimplePats` now takes `SynPat` instead of `SynSimplePat list`. ([PR #16425](https://github.com/dotnet/fsharp/pull/16425))
+* Reduce allocations in compiler checking via `ValueOption` usage ([PR #16323](https://github.com/dotnet/fsharp/pull/16323), [PR #16567](https://github.com/dotnet/fsharp/pull/16567))
+* Reverted [#16348](https://github.com/dotnet/fsharp/pull/16348) `ThreadStatic` `CancellationToken` changes to improve test stability and prevent potential unwanted cancellations. ([PR #16536](https://github.com/dotnet/fsharp/pull/16536))
+* Refactored parenthesization API. ([PR #16461])(https://github.com/dotnet/fsharp/pull/16461))
diff --git a/docs/release-notes/.FSharp.Core/8.0.300.md b/docs/release-notes/.FSharp.Core/8.0.300.md
new file mode 100644
index 00000000000..9acf7d07635
--- /dev/null
+++ b/docs/release-notes/.FSharp.Core/8.0.300.md
@@ -0,0 +1,3 @@
+### Fixed
+
+* Preserve original stack traces in resumable state machines generated code if available. ([PR #16568](https://github.com/dotnet/fsharp/pull/16568))
\ No newline at end of file
diff --git a/docs/release-notes/.VisualStudio/17.10.md b/docs/release-notes/.VisualStudio/17.10.md
new file mode 100644
index 00000000000..0045b1bd64b
--- /dev/null
+++ b/docs/release-notes/.VisualStudio/17.10.md
@@ -0,0 +1,7 @@
+### Fixed
+
+* Show signature help mid-pipeline in more scenarios. ([PR #16462](https://github.com/dotnet/fsharp/pull/16462))
+
+### Changed
+
+* Use refactored parenthesization API in unnecessary parentheses code fix. ([PR #16461])(https://github.com/dotnet/fsharp/pull/16461))
diff --git a/docs/release-notes/.VisualStudio/17.9.md b/docs/release-notes/.VisualStudio/17.9.md
new file mode 100644
index 00000000000..47d796020d5
--- /dev/null
+++ b/docs/release-notes/.VisualStudio/17.9.md
@@ -0,0 +1,3 @@
+### Added
+
+* Analyzer & code fix for removing unnecessary parentheses. ([PR #16079](https://github.com/dotnet/fsharp/pull/16079) et seq.)
diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml
index bb7133e0c11..732b37657ea 100644
--- a/eng/Version.Details.xml
+++ b/eng/Version.Details.xml
@@ -1,9 +1,9 @@
-
+
https://github.com/dotnet/source-build-reference-packages
- f2c3fed62861b918dfe300f01b497551813a56df
+ 412264fd6c04712d1d31ff05d37c6919101ef4f4
@@ -29,9 +29,9 @@
-
+
https://github.com/dotnet/arcade
- 888985fb9a9ae4cb30bca75f98af9126c839e660
+ 4c941e2e3ae61502bd4ffd711930f662fd808375
diff --git a/eng/Versions.props b/eng/Versions.props
index b27ad4ce92d..d62e0635a85 100644
--- a/eng/Versions.props
+++ b/eng/Versions.props
@@ -91,7 +91,7 @@
6.0.0
4.5.0
- 4.6.0-2.23126.2
+ 4.6.0-3.23329.3
17.7.25-preview
17.7.35338-preview.1
17.7.58-pre
@@ -168,7 +168,7 @@
1.0.0
1.1.33
- 0.13.2
+ 0.13.10
2.16.5
4.3.0.0
1.0.31
@@ -192,7 +192,6 @@
2.4.2
5.10.3
2.2.0
- 1.0.0-beta.23475.1
1.0.0-prerelease.23614.4
1.0.0-prerelease.23614.4
diff --git a/global.json b/global.json
index b5234bc1761..56134e06fd6 100644
--- a/global.json
+++ b/global.json
@@ -17,7 +17,7 @@
"perl": "5.38.0.1"
},
"msbuild-sdks": {
- "Microsoft.DotNet.Arcade.Sdk": "8.0.0-beta.24060.4",
+ "Microsoft.DotNet.Arcade.Sdk": "8.0.0-beta.24073.2",
"Microsoft.DotNet.Helix.Sdk": "8.0.0-beta.23255.2"
}
}
diff --git a/src/Compiler/Checking/CheckComputationExpressions.fs b/src/Compiler/Checking/CheckComputationExpressions.fs
index 7a64721f901..f0727c3fc5d 100644
--- a/src/Compiler/Checking/CheckComputationExpressions.fs
+++ b/src/Compiler/Checking/CheckComputationExpressions.fs
@@ -43,21 +43,23 @@ let TryFindIntrinsicOrExtensionMethInfo collectionSettings (cenv: cenv) (env: Tc
/// Ignores an attribute
let IgnoreAttribute _ = None
+[]
let (|ExprAsPat|_|) (f: SynExpr) =
match f with
| SingleIdent v1
- | SynExprParen(SingleIdent v1, _, _, _) -> Some(mkSynPatVar None v1)
+ | SynExprParen(SingleIdent v1, _, _, _) -> ValueSome(mkSynPatVar None v1)
| SynExprParen(SynExpr.Tuple(false, elems, commas, _), _, _, _) ->
let elems = elems |> List.map (|SingleIdent|_|)
if elems |> List.forall (fun x -> x.IsSome) then
- Some(SynPat.Tuple(false, (elems |> List.map (fun x -> mkSynPatVar None x.Value)), commas, f.Range))
+ ValueSome(SynPat.Tuple(false, (elems |> List.map (fun x -> mkSynPatVar None x.Value)), commas, f.Range))
else
- None
- | _ -> None
+ ValueNone
+ | _ -> ValueNone
// For join clauses that join on nullable, we syntactically insert the creation of nullable values on the appropriate side of the condition,
// then pull the syntax apart again
+[]
let (|JoinRelation|_|) cenv env (expr: SynExpr) =
let m = expr.Range
let ad = env.eAccessRights
@@ -79,27 +81,27 @@ let (|JoinRelation|_|) cenv env (expr: SynExpr) =
| _ -> false
match expr with
- | BinOpExpr(opId, a, b) when isOpName opNameEquals cenv.g.equals_operator_vref opId.idText -> Some(a, b)
+ | BinOpExpr(opId, a, b) when isOpName opNameEquals cenv.g.equals_operator_vref opId.idText -> ValueSome(a, b)
| BinOpExpr(opId, a, b) when isOpName opNameEqualsNullable cenv.g.equals_nullable_operator_vref opId.idText ->
let a =
SynExpr.App(ExprAtomicFlag.Atomic, false, mkSynLidGet a.Range [ MangledGlobalName; "System" ] "Nullable", a, a.Range)
- Some(a, b)
+ ValueSome(a, b)
| BinOpExpr(opId, a, b) when isOpName opNameNullableEquals cenv.g.nullable_equals_operator_vref opId.idText ->
let b =
SynExpr.App(ExprAtomicFlag.Atomic, false, mkSynLidGet b.Range [ MangledGlobalName; "System" ] "Nullable", b, b.Range)
- Some(a, b)
+ ValueSome(a, b)
| BinOpExpr(opId, a, b) when isOpName opNameNullableEqualsNullable cenv.g.nullable_equals_nullable_operator_vref opId.idText ->
- Some(a, b)
+ ValueSome(a, b)
- | _ -> None
+ | _ -> ValueNone
let elimFastIntegerForLoop (spFor, spTo, id, start: SynExpr, dir, finish: SynExpr, innerExpr, m: range) =
let mOp = (unionRanges start.Range finish.Range).MakeSynthetic()
@@ -179,7 +181,7 @@ let YieldFree (cenv: cenv) expr =
/// Determine if a syntactic expression inside 'seq { ... }' or '[...]' counts as a "simple sequence
/// of semicolon separated values". For example [1;2;3].
/// 'acceptDeprecated' is true for the '[ ... ]' case, where we allow the syntax '[ if g then t else e ]' but ask it to be parenthesized
-///
+[]
let (|SimpleSemicolonSequence|_|) cenv acceptDeprecated cexpr =
let IsSimpleSemicolonSequenceElement expr =
@@ -207,12 +209,12 @@ let (|SimpleSemicolonSequence|_|) cenv acceptDeprecated cexpr =
if IsSimpleSemicolonSequenceElement e1 then
TryGetSimpleSemicolonSequenceOfComprehension e2 (e1 :: acc)
else
- None
+ ValueNone
| _ ->
if IsSimpleSemicolonSequenceElement expr then
- Some(List.rev (expr :: acc))
+ ValueSome(List.rev (expr :: acc))
else
- None
+ ValueNone
TryGetSimpleSemicolonSequenceOfComprehension cexpr []
diff --git a/src/Compiler/Checking/CheckDeclarations.fs b/src/Compiler/Checking/CheckDeclarations.fs
index 6f9c0a21d53..c5e21e744cd 100644
--- a/src/Compiler/Checking/CheckDeclarations.fs
+++ b/src/Compiler/Checking/CheckDeclarations.fs
@@ -866,7 +866,8 @@ module AddAugmentationDeclarations =
let ShouldAugmentUnion (g: TcGlobals) (tycon: Tycon) =
g.langVersion.SupportsFeature LanguageFeature.UnionIsPropertiesVisible &&
- HasDefaultAugmentationAttribute g (mkLocalTyconRef tycon)
+ HasDefaultAugmentationAttribute g (mkLocalTyconRef tycon) &&
+ tycon.UnionCasesArray.Length > 1
let AddUnionAugmentationValues (cenv: cenv) (env: TcEnv) tycon =
let tcref = mkLocalTyconRef tycon
@@ -2941,7 +2942,7 @@ module EstablishTypeDefinitionCores =
| Some (tc, args, m) ->
let ad = envinner.AccessRights
match ResolveTypeLongIdent cenv.tcSink cenv.nameResolver ItemOccurence.UseInType OpenQualified envinner.NameEnv ad tc TypeNameResolutionStaticArgsInfo.DefiniteEmpty PermitDirectReferenceToGeneratedType.Yes with
- | Result (_, tcrefBeforeStaticArguments) when
+ | Result (_, tcrefBeforeStaticArguments, _) when
tcrefBeforeStaticArguments.IsProvided &&
not tcrefBeforeStaticArguments.IsErased ->
@@ -4117,11 +4118,11 @@ module TcDeclarations =
| _ ->
let resInfo = TypeNameResolutionStaticArgsInfo.FromTyArgs synTypars.Length
- let _, tcref =
+ let tcref =
match ResolveTypeLongIdent cenv.tcSink cenv.nameResolver ItemOccurence.Binding OpenQualified envForDecls.NameEnv ad longPath resInfo PermitDirectReferenceToGeneratedType.No with
| Result res ->
// Update resolved type parameters with the names from the source.
- let _, tcref = res
+ let _, tcref, _ = res
if tcref.TyparsNoRange.Length = synTypars.Length then
(tcref.TyparsNoRange, synTypars)
||> List.zip
@@ -4131,11 +4132,12 @@ module TcDeclarations =
typar.SetIdent(untypedIdent)
)
- res
- | res when inSig && List.isSingleton longPath ->
- errorR(Deprecated(FSComp.SR.tcReservedSyntaxForAugmentation(), m))
- ForceRaise res
- | res -> ForceRaise res
+ tcref
+
+ | Exception exn ->
+ if inSig && List.isSingleton longPath then
+ errorR(Deprecated(FSComp.SR.tcReservedSyntaxForAugmentation(), m))
+ ForceRaise (Exception exn)
tcref
let isInterfaceOrDelegateOrEnum =
@@ -5595,7 +5597,7 @@ let SolveInternalUnknowns g (cenv: cenv) denvAtEnd moduleContents extraAttribs =
if (tp.Rigidity <> TyparRigidity.Rigid) && not tp.IsSolved then
ChooseTyparSolutionAndSolve cenv.css denvAtEnd tp
-let CheckModuleSignature g (cenv: cenv) m denvAtEnd rootSigOpt implFileTypePriorToSig implFileSpecPriorToSig moduleContents =
+let CheckModuleSignature g (cenv: cenv) m denvAtEnd rootSigOpt implFileTypePriorToSig implFileSpecPriorToSig moduleContents fileName qualifiedNameOfFile =
match rootSigOpt with
| None ->
// Deep copy the inferred type of the module
@@ -5603,7 +5605,13 @@ let CheckModuleSignature g (cenv: cenv) m denvAtEnd rootSigOpt implFileTypePrior
(implFileTypePriorToSigCopied, moduleContents)
- | Some sigFileType ->
+ | Some sigFileType ->
+ use _ =
+ Activity.start "CheckDeclarations.CheckModuleSignature"
+ [|
+ Activity.Tags.fileName, fileName
+ Activity.Tags.qualifiedNameOfFile, qualifiedNameOfFile
+ |]
// We want to show imperative type variables in any types in error messages at this late point
let denv = { denvAtEnd with showInferenceTyparAnnotations=true }
@@ -5729,7 +5737,7 @@ let CheckOneImplFile
// Check the module matches the signature
let implFileTy, implFileContents =
conditionallySuppressErrorReporting (checkForErrors()) (fun () ->
- CheckModuleSignature g cenv m denvAtEnd rootSigOpt implFileTypePriorToSig implFileSpecPriorToSig moduleContents)
+ CheckModuleSignature g cenv m denvAtEnd rootSigOpt implFileTypePriorToSig implFileSpecPriorToSig moduleContents fileName qualNameOfFile.Text)
do
conditionallySuppressErrorReporting (checkForErrors()) (fun () ->
diff --git a/src/Compiler/Checking/CheckExpressions.fs b/src/Compiler/Checking/CheckExpressions.fs
index f182c98ac85..2cac6dfbec3 100644
--- a/src/Compiler/Checking/CheckExpressions.fs
+++ b/src/Compiler/Checking/CheckExpressions.fs
@@ -767,7 +767,7 @@ let TcConst (cenv: cenv) (overallTy: TType) m env synConst =
| SynMeasure.One _ -> Measure.One
| SynMeasure.Named(tc, m) ->
let ad = env.eAccessRights
- let _, tcref = ForceRaise(ResolveTypeLongIdent cenv.tcSink cenv.nameResolver ItemOccurence.Use OpenQualified env.eNameResEnv ad tc TypeNameResolutionStaticArgsInfo.DefiniteEmpty PermitDirectReferenceToGeneratedType.No)
+ let _, tcref, _ = ForceRaise(ResolveTypeLongIdent cenv.tcSink cenv.nameResolver ItemOccurence.Use OpenQualified env.eNameResEnv ad tc TypeNameResolutionStaticArgsInfo.DefiniteEmpty PermitDirectReferenceToGeneratedType.No)
match tcref.TypeOrMeasureKind with
| TyparKind.Type -> error(Error(FSComp.SR.tcExpectedUnitOfMeasureNotType(), m))
| TyparKind.Measure -> Measure.Const tcref
@@ -1895,7 +1895,7 @@ let BuildFieldMap (cenv: cenv) env isPartial ty (flds: ((Ident list * Ident) * '
| _ -> error(Error(FSComp.SR.tcRecordFieldInconsistentTypes(), m)))
Some(tinst, tcref, fldsmap, List.rev rfldsList)
-let rec ApplyUnionCaseOrExn (makerForUnionCase, makerForExnTag) m (cenv: cenv) env overallTy item =
+let ApplyUnionCaseOrExn (makerForUnionCase, makerForExnTag) m (cenv: cenv) env overallTy item =
let g = cenv.g
let ad = env.eAccessRights
match item with
@@ -3115,15 +3115,17 @@ let BuildRecdFieldSet g m objExpr (rfinfo: RecdFieldInfo) argExpr =
// Helpers dealing with named and optional args at callsites
//-------------------------------------------------------------------------
+[]
let (|BinOpExpr|_|) expr =
match expr with
- | SynExpr.App (_, _, SynExpr.App (_, _, SingleIdent opId, a, _), b, _) -> Some (opId, a, b)
- | _ -> None
+ | SynExpr.App (_, _, SynExpr.App (_, _, SingleIdent opId, a, _), b, _) -> ValueSome (opId, a, b)
+ | _ -> ValueNone
+[]
let (|SimpleEqualsExpr|_|) expr =
match expr with
- | BinOpExpr(opId, a, b) when opId.idText = opNameEquals -> Some (a, b)
- | _ -> None
+ | BinOpExpr(opId, a, b) when opId.idText = opNameEquals -> ValueSome (a, b)
+ | _ -> ValueNone
/// Detect a named argument at a callsite
let TryGetNamedArg expr =
@@ -4458,7 +4460,7 @@ and TcLongIdentType kindOpt (cenv: cenv) newOk checkConstraints occ iwsam env tp
let m = synLongId.Range
let ad = env.eAccessRights
- let tinstEnclosing, tcref = ForceRaise(ResolveTypeLongIdent cenv.tcSink cenv.nameResolver occ OpenQualified env.NameEnv ad tc TypeNameResolutionStaticArgsInfo.DefiniteEmpty PermitDirectReferenceToGeneratedType.No)
+ let tinstEnclosing, tcref, inst = ForceRaise(ResolveTypeLongIdent cenv.tcSink cenv.nameResolver occ OpenQualified env.NameEnv ad tc TypeNameResolutionStaticArgsInfo.DefiniteEmpty PermitDirectReferenceToGeneratedType.No)
CheckIWSAM cenv env checkConstraints iwsam m tcref
@@ -4472,7 +4474,7 @@ and TcLongIdentType kindOpt (cenv: cenv) newOk checkConstraints occ iwsam env tp
| _, TyparKind.Measure ->
TType_measure (Measure.Const tcref), tpenv
| _, TyparKind.Type ->
- TcTypeApp cenv newOk checkConstraints occ env tpenv m tcref tinstEnclosing []
+ TcTypeApp cenv newOk checkConstraints occ env tpenv m tcref tinstEnclosing [] inst
/// Some.Long.TypeName
/// ty1 SomeLongTypeName
@@ -4480,7 +4482,7 @@ and TcLongIdentAppType kindOpt (cenv: cenv) newOk checkConstraints occ iwsam env
let (SynLongIdent(tc, _, _)) = longId
let ad = env.eAccessRights
- let tinstEnclosing, tcref =
+ let tinstEnclosing, tcref, inst =
let tyResInfo = TypeNameResolutionStaticArgsInfo.FromTyArgs args.Length
ResolveTypeLongIdent cenv.tcSink cenv.nameResolver ItemOccurence.UseInType OpenQualified env.eNameResEnv ad tc tyResInfo PermitDirectReferenceToGeneratedType.No
|> ForceRaise
@@ -4499,7 +4501,7 @@ and TcLongIdentAppType kindOpt (cenv: cenv) newOk checkConstraints occ iwsam env
| _, TyparKind.Type ->
if postfix && tcref.Typars m |> List.exists (fun tp -> match tp.Kind with TyparKind.Measure -> true | _ -> false) then
error(Error(FSComp.SR.tcInvalidUnitsOfMeasurePrefix(), m))
- TcTypeApp cenv newOk checkConstraints occ env tpenv m tcref tinstEnclosing args
+ TcTypeApp cenv newOk checkConstraints occ env tpenv m tcref tinstEnclosing args inst
| _, TyparKind.Measure ->
match args, postfix with
@@ -4518,8 +4520,8 @@ and TcNestedAppType (cenv: cenv) newOk checkConstraints occ iwsam env tpenv synL
let leftTy, tpenv = TcType cenv newOk checkConstraints occ iwsam env tpenv synLeftTy
match leftTy with
| AppTy g (tcref, tinst) ->
- let tcref = ResolveTypeLongIdentInTyconRef cenv.tcSink cenv.nameResolver env.eNameResEnv (TypeNameResolutionInfo.ResolveToTypeRefs (TypeNameResolutionStaticArgsInfo.FromTyArgs args.Length)) ad m tcref longId
- TcTypeApp cenv newOk checkConstraints occ env tpenv m tcref tinst args
+ let tcref, inst = ResolveTypeLongIdentInTyconRef cenv.tcSink cenv.nameResolver env.eNameResEnv (TypeNameResolutionInfo.ResolveToTypeRefs (TypeNameResolutionStaticArgsInfo.FromTyArgs args.Length)) ad m tcref longId
+ TcTypeApp cenv newOk checkConstraints occ env tpenv m tcref tinst args inst
| _ ->
error(Error(FSComp.SR.tcTypeHasNoNestedTypes(), m))
@@ -4943,7 +4945,7 @@ and TcProvidedTypeApp (cenv: cenv) env tpenv tcref args m =
/// Note that the generic type may be a nested generic type List.ListEnumerator.
/// In this case, 'argsR is only the instantiation of the suffix type arguments, and pathTypeArgs gives
/// the prefix of type arguments.
-and TcTypeApp (cenv: cenv) newOk checkConstraints occ env tpenv m tcref pathTypeArgs (synArgTys: SynType list) =
+and TcTypeApp (cenv: cenv) newOk checkConstraints occ env tpenv m tcref pathTypeArgs (synArgTys: SynType list) (tinst: TypeInst) =
let g = cenv.g
CheckTyconAccessible cenv.amap m env.AccessRights tcref |> ignore
CheckEntityAttributes g tcref m |> CommitOperationResult
@@ -4954,16 +4956,22 @@ and TcTypeApp (cenv: cenv) newOk checkConstraints occ env tpenv m tcref pathType
if tcref.Deref.IsProvided then TcProvidedTypeApp cenv env tpenv tcref synArgTys m else
#endif
- let tps, _, tinst, _ = FreshenTyconRef2 g m tcref
-
- // If we're not checking constraints, i.e. when we first assert the super/interfaces of a type definition, then just
- // clear the constraint lists of the freshly generated type variables. A little ugly but fairly localized.
- if checkConstraints = NoCheckCxs then tps |> List.iter (fun tp -> tp.SetConstraints [])
let synArgTysLength = synArgTys.Length
let pathTypeArgsLength = pathTypeArgs.Length
if tinst.Length <> pathTypeArgsLength + synArgTysLength then
error (TyconBadArgs(env.DisplayEnv, tcref, pathTypeArgsLength + synArgTysLength, m))
+ let tps = tinst |> List.skip pathTypeArgsLength |> List.map (fun t ->
+ match t with
+ | TType_var(typar, _)
+ | TType_measure(Measure.Var typar) -> typar
+ | t -> failwith $"TcTypeApp: {t}"
+ )
+
+ // If we're not checking constraints, i.e. when we first assert the super/interfaces of a type definition, then just
+ // clear the constraint lists of the freshly generated type variables. A little ugly but fairly localized.
+ if checkConstraints = NoCheckCxs then tps |> List.iter (fun tp -> tp.SetConstraints [])
+
let argTys, tpenv =
// Get the suffix of typars
let tpsForArgs = List.skip (tps.Length - synArgTysLength) tps
@@ -5009,9 +5017,9 @@ and TcNestedTypeApplication (cenv: cenv) newOk checkConstraints occ iwsam env tp
error(Error(FSComp.SR.tcTypeHasNoNestedTypes(), mWholeTypeApp))
match ty with
- | TType_app(tcref, _, _) ->
+ | TType_app(tcref, inst, _) ->
CheckIWSAM cenv env checkConstraints iwsam mWholeTypeApp tcref
- TcTypeApp cenv newOk checkConstraints occ env tpenv mWholeTypeApp tcref pathTypeArgs tyargs
+ TcTypeApp cenv newOk checkConstraints occ env tpenv mWholeTypeApp tcref pathTypeArgs tyargs inst
| _ ->
error(InternalError("TcNestedTypeApplication: expected type application", mWholeTypeApp))
@@ -8163,10 +8171,10 @@ and TcNameOfExpr (cenv: cenv) env tpenv (synArg: SynExpr) =
if (match delayed with [DelayedTypeApp _] | [] -> true | _ -> false) then
let (TypeNameResolutionInfo(_, staticArgsInfo)) = GetLongIdentTypeNameInfo delayed
match ResolveTypeLongIdent cenv.tcSink cenv.nameResolver ItemOccurence.UseInAttribute OpenQualified env.eNameResEnv ad longId staticArgsInfo PermitDirectReferenceToGeneratedType.No with
- | Result (tinstEnclosing, tcref) when IsEntityAccessible cenv.amap m ad tcref ->
+ | Result (tinstEnclosing, tcref, inst) when IsEntityAccessible cenv.amap m ad tcref ->
match delayed with
| [DelayedTypeApp (tyargs, _, mExprAndTypeArgs)] ->
- TcTypeApp cenv NewTyparsOK CheckCxs ItemOccurence.UseInType env tpenv mExprAndTypeArgs tcref tinstEnclosing tyargs |> ignore
+ TcTypeApp cenv NewTyparsOK CheckCxs ItemOccurence.UseInType env tpenv mExprAndTypeArgs tcref tinstEnclosing tyargs inst |> ignore
| _ -> ()
true // resolved to a type name, done with checks
| _ ->
@@ -10858,7 +10866,7 @@ and TcAttributeEx canFail (cenv: cenv) (env: TcEnv) attrTgt attrEx (synAttr: Syn
match ResolveTypeLongIdent cenv.tcSink cenv.nameResolver ItemOccurence.UseInAttribute OpenQualified env.eNameResEnv ad tycon TypeNameResolutionStaticArgsInfo.DefiniteEmpty PermitDirectReferenceToGeneratedType.No with
| Exception err -> raze err
- | Result(tinstEnclosing, tcref) -> success(TcTypeApp cenv NoNewTypars CheckCxs ItemOccurence.UseInAttribute env tpenv mAttr tcref tinstEnclosing [])
+ | Result(tinstEnclosing, tcref, inst) -> success(TcTypeApp cenv NoNewTypars CheckCxs ItemOccurence.UseInAttribute env tpenv mAttr tcref tinstEnclosing [] inst)
ForceRaise ((try1 (tyid.idText + "Attribute")) |> otherwise (fun () -> (try1 tyid.idText)))
diff --git a/src/Compiler/Checking/CheckExpressions.fsi b/src/Compiler/Checking/CheckExpressions.fsi
index 3f1e8d78b97..852fee213e6 100644
--- a/src/Compiler/Checking/CheckExpressions.fsi
+++ b/src/Compiler/Checking/CheckExpressions.fsi
@@ -712,7 +712,8 @@ val TcMatchPattern:
synWhenExprOpt: SynExpr option ->
Pattern * Expr option * Val list * TcEnv * UnscopedTyparEnv
-val (|BinOpExpr|_|): SynExpr -> (Ident * SynExpr * SynExpr) option
+[]
+val (|BinOpExpr|_|): SynExpr -> (Ident * SynExpr * SynExpr) voption
/// Check a set of let bindings in a class or module
val TcLetBindings:
diff --git a/src/Compiler/Checking/ConstraintSolver.fs b/src/Compiler/Checking/ConstraintSolver.fs
index c30ca0cdc66..2667ea6ccfc 100644
--- a/src/Compiler/Checking/ConstraintSolver.fs
+++ b/src/Compiler/Checking/ConstraintSolver.fs
@@ -57,97 +57,18 @@ open FSharp.Compiler.Import
open FSharp.Compiler.InfoReader
open FSharp.Compiler.Infos
open FSharp.Compiler.MethodCalls
+open FSharp.Compiler.NameResolution
open FSharp.Compiler.Syntax
open FSharp.Compiler.Syntax.PrettyNaming
open FSharp.Compiler.SyntaxTreeOps
open FSharp.Compiler.TcGlobals
open FSharp.Compiler.Text
-open FSharp.Compiler.Text.Range
open FSharp.Compiler.TypedTree
open FSharp.Compiler.TypedTreeBasics
open FSharp.Compiler.TypedTreeOps
open FSharp.Compiler.TypeHierarchy
open FSharp.Compiler.TypeRelations
-//-------------------------------------------------------------------------
-// Generate type variables and record them in within the scope of the
-// compilation environment, which currently corresponds to the scope
-// of the constraint resolution carried out by type checking.
-//-------------------------------------------------------------------------
-
-let compgenId = mkSynId range0 unassignedTyparName
-
-let NewCompGenTypar (kind, rigid, staticReq, dynamicReq, error) =
- Construct.NewTypar(kind, rigid, SynTypar(compgenId, staticReq, true), error, dynamicReq, [], false, false)
-
-let AnonTyparId m = mkSynId m unassignedTyparName
-
-let NewAnonTypar (kind, m, rigid, var, dyn) =
- Construct.NewTypar (kind, rigid, SynTypar(AnonTyparId m, var, true), false, dyn, [], false, false)
-
-let NewNamedInferenceMeasureVar (_m, rigid, var, id) =
- Construct.NewTypar(TyparKind.Measure, rigid, SynTypar(id, var, false), false, TyparDynamicReq.No, [], false, false)
-
-let NewInferenceMeasurePar () =
- NewCompGenTypar (TyparKind.Measure, TyparRigidity.Flexible, TyparStaticReq.None, TyparDynamicReq.No, false)
-
-let NewErrorTypar () =
- NewCompGenTypar (TyparKind.Type, TyparRigidity.Flexible, TyparStaticReq.None, TyparDynamicReq.No, true)
-
-let NewErrorMeasureVar () =
- NewCompGenTypar (TyparKind.Measure, TyparRigidity.Flexible, TyparStaticReq.None, TyparDynamicReq.No, true)
-
-let NewInferenceType (g: TcGlobals) =
- ignore g // included for future, minimizing code diffs, see https://github.com/dotnet/fsharp/pull/6804
- mkTyparTy (Construct.NewTypar (TyparKind.Type, TyparRigidity.Flexible, SynTypar(compgenId, TyparStaticReq.None, true), false, TyparDynamicReq.No, [], false, false))
-
-let NewErrorType () =
- mkTyparTy (NewErrorTypar ())
-
-let NewErrorMeasure () =
- Measure.Var (NewErrorMeasureVar ())
-
-let NewByRefKindInferenceType (g: TcGlobals) m =
- let tp = Construct.NewTypar (TyparKind.Type, TyparRigidity.Flexible, SynTypar(compgenId, TyparStaticReq.HeadType, true), false, TyparDynamicReq.No, [], false, false)
- if g.byrefkind_InOut_tcr.CanDeref then
- tp.SetConstraints [TyparConstraint.DefaultsTo(10, TType_app(g.byrefkind_InOut_tcr, [], g.knownWithoutNull), m)]
- mkTyparTy tp
-
-let NewInferenceTypes g l = l |> List.map (fun _ -> NewInferenceType g)
-
-let FreshenTypar (g: TcGlobals) rigid (tp: Typar) =
- let clearStaticReq = g.langVersion.SupportsFeature LanguageFeature.InterfacesWithAbstractStaticMembers
- let staticReq = if clearStaticReq then TyparStaticReq.None else tp.StaticReq
- let dynamicReq = if rigid = TyparRigidity.Rigid then TyparDynamicReq.Yes else TyparDynamicReq.No
- NewCompGenTypar (tp.Kind, rigid, staticReq, dynamicReq, false)
-
-// QUERY: should 'rigid' ever really be 'true'? We set this when we know
-// we are going to have to generalize a typar, e.g. when implementing a
-// abstract generic method slot. But we later check the generalization
-// condition anyway, so we could get away with a non-rigid typar. This
-// would sort of be cleaner, though give errors later.
-let FreshenAndFixupTypars g m rigid fctps tinst tpsorig =
- let tps = tpsorig |> List.map (FreshenTypar g rigid)
- let renaming, tinst = FixupNewTypars m fctps tinst tpsorig tps
- tps, renaming, tinst
-
-let FreshenTypeInst g m tpsorig =
- FreshenAndFixupTypars g m TyparRigidity.Flexible [] [] tpsorig
-
-let FreshMethInst g m fctps tinst tpsorig =
- FreshenAndFixupTypars g m TyparRigidity.Flexible fctps tinst tpsorig
-
-let FreshenTypars g m tpsorig =
- match tpsorig with
- | [] -> []
- | _ ->
- let _, _, tpTys = FreshenTypeInst g m tpsorig
- tpTys
-
-let FreshenMethInfo m (minfo: MethInfo) =
- let _, _, tpTys = FreshMethInst minfo.TcGlobals m (minfo.GetFormalTyparsOfDeclaringType m) minfo.DeclaringTypeInst minfo.FormalMethodTypars
- tpTys
-
//-------------------------------------------------------------------------
// Unification of types: solve/record equality constraints
// Subsumption of types: solve/record subtyping constraints
@@ -1718,8 +1639,8 @@ and SolveMemberConstraint (csenv: ConstraintSolverEnv) ignoreUnresolvedOverload
let propName = nm[4..]
let props =
supportTys |> List.choose (fun ty ->
- match NameResolution.TryFindAnonRecdFieldOfType g ty propName with
- | Some (NameResolution.Item.AnonRecdField(anonInfo, tinst, i, _)) -> Some (anonInfo, tinst, i)
+ match TryFindAnonRecdFieldOfType g ty propName with
+ | Some (Item.AnonRecdField(anonInfo, tinst, i, _)) -> Some (anonInfo, tinst, i)
| _ -> None)
match props with
| [ prop ] -> Some prop
diff --git a/src/Compiler/Checking/ConstraintSolver.fsi b/src/Compiler/Checking/ConstraintSolver.fsi
index eb48ce3b439..aab7c04dfec 100644
--- a/src/Compiler/Checking/ConstraintSolver.fsi
+++ b/src/Compiler/Checking/ConstraintSolver.fsi
@@ -15,61 +15,6 @@ open FSharp.Compiler.Text
open FSharp.Compiler.TypedTree
open FSharp.Compiler.TypedTreeOps
-/// Create a type variable representing the use of a "_" in F# code
-val NewAnonTypar: TyparKind * range * TyparRigidity * TyparStaticReq * TyparDynamicReq -> Typar
-
-/// Create an inference type variable
-val NewInferenceType: TcGlobals -> TType
-
-/// Create an inference type variable for the kind of a byref pointer
-val NewByRefKindInferenceType: TcGlobals -> range -> TType
-
-/// Create an inference type variable representing an error condition when checking an expression
-val NewErrorType: unit -> TType
-
-/// Create an inference type variable representing an error condition when checking a measure
-val NewErrorMeasure: unit -> Measure
-
-/// Create a list of inference type variables, one for each element in the input list
-val NewInferenceTypes: TcGlobals -> 'T list -> TType list
-
-/// Given a set of formal type parameters and their constraints, make new inference type variables for
-/// each and ensure that the constraints on the new type variables are adjusted to refer to these.
-///
-/// Returns
-/// 1. the new type parameters
-/// 2. the instantiation mapping old type parameters to inference variables
-/// 3. the inference type variables as a list of types.
-val FreshenAndFixupTypars:
- g: TcGlobals ->
- m: range ->
- rigid: TyparRigidity ->
- Typars ->
- TType list ->
- Typars ->
- Typars * TyparInstantiation * TType list
-
-/// Given a set of type parameters, make new inference type variables for
-/// each and ensure that the constraints on the new type variables are adjusted.
-///
-/// Returns
-/// 1. the new type parameters
-/// 2. the instantiation mapping old type parameters to inference variables
-/// 3. the inference type variables as a list of types.
-val FreshenTypeInst: g: TcGlobals -> range -> Typars -> Typars * TyparInstantiation * TType list
-
-/// Given a set of type parameters, make new inference type variables for
-/// each and ensure that the constraints on the new type variables are adjusted.
-///
-/// Returns the inference type variables as a list of types.
-val FreshenTypars: g: TcGlobals -> range -> Typars -> TType list
-
-/// Given a method, which may be generic, make new inference type variables for
-/// its generic parameters, and ensure that the constraints the new type variables are adjusted.
-///
-/// Returns the inference type variables as a list of types.
-val FreshenMethInfo: range -> MethInfo -> TType list
-
/// Information about the context of a type equation.
[]
type ContextInfo =
diff --git a/src/Compiler/Checking/NameResolution.fs b/src/Compiler/Checking/NameResolution.fs
index 5afdada9b05..21bfa7a2308 100644
--- a/src/Compiler/Checking/NameResolution.fs
+++ b/src/Compiler/Checking/NameResolution.fs
@@ -1566,6 +1566,85 @@ let FreshenUnionCaseRef (ncenv: NameResolver) m (ucref: UnionCaseRef) =
let FreshenRecdFieldRef (ncenv: NameResolver) m (rfref: RecdFieldRef) =
RecdFieldInfo(ncenv.InstantiationGenerator m (rfref.Tycon.Typars m), rfref)
+//-------------------------------------------------------------------------
+// Generate type variables and record them in within the scope of the
+// compilation environment, which currently corresponds to the scope
+// of the constraint resolution carried out by type checking.
+//-------------------------------------------------------------------------
+
+let compgenId = mkSynId range0 unassignedTyparName
+
+let NewCompGenTypar (kind, rigid, staticReq, dynamicReq, error) =
+ Construct.NewTypar(kind, rigid, SynTypar(compgenId, staticReq, true), error, dynamicReq, [], false, false)
+
+let AnonTyparId m = mkSynId m unassignedTyparName
+
+let NewAnonTypar (kind, m, rigid, var, dyn) =
+ Construct.NewTypar (kind, rigid, SynTypar(AnonTyparId m, var, true), false, dyn, [], false, false)
+
+let NewNamedInferenceMeasureVar (_m: range, rigid, var, id) =
+ Construct.NewTypar(TyparKind.Measure, rigid, SynTypar(id, var, false), false, TyparDynamicReq.No, [], false, false)
+
+let NewInferenceMeasurePar () =
+ NewCompGenTypar (TyparKind.Measure, TyparRigidity.Flexible, TyparStaticReq.None, TyparDynamicReq.No, false)
+
+let NewErrorTypar () =
+ NewCompGenTypar (TyparKind.Type, TyparRigidity.Flexible, TyparStaticReq.None, TyparDynamicReq.No, true)
+
+let NewErrorMeasureVar () =
+ NewCompGenTypar (TyparKind.Measure, TyparRigidity.Flexible, TyparStaticReq.None, TyparDynamicReq.No, true)
+
+let NewInferenceType (g: TcGlobals) =
+ ignore g // included for future, minimizing code diffs, see https://github.com/dotnet/fsharp/pull/6804
+ mkTyparTy (Construct.NewTypar (TyparKind.Type, TyparRigidity.Flexible, SynTypar(compgenId, TyparStaticReq.None, true), false, TyparDynamicReq.No, [], false, false))
+
+let NewErrorType () =
+ mkTyparTy (NewErrorTypar ())
+
+let NewErrorMeasure () =
+ Measure.Var (NewErrorMeasureVar ())
+
+let NewByRefKindInferenceType (g: TcGlobals) m =
+ let tp = Construct.NewTypar (TyparKind.Type, TyparRigidity.Flexible, SynTypar(compgenId, TyparStaticReq.HeadType, true), false, TyparDynamicReq.No, [], false, false)
+ if g.byrefkind_InOut_tcr.CanDeref then
+ tp.SetConstraints [TyparConstraint.DefaultsTo(10, TType_app(g.byrefkind_InOut_tcr, [], g.knownWithoutNull), m)]
+ mkTyparTy tp
+
+let NewInferenceTypes g l = l |> List.map (fun _ -> NewInferenceType g)
+
+let FreshenTypar (g: TcGlobals) rigid (tp: Typar) =
+ let clearStaticReq = g.langVersion.SupportsFeature LanguageFeature.InterfacesWithAbstractStaticMembers
+ let staticReq = if clearStaticReq then TyparStaticReq.None else tp.StaticReq
+ let dynamicReq = if rigid = TyparRigidity.Rigid then TyparDynamicReq.Yes else TyparDynamicReq.No
+ NewCompGenTypar (tp.Kind, rigid, staticReq, dynamicReq, false)
+
+// QUERY: should 'rigid' ever really be 'true'? We set this when we know
+// we are going to have to generalize a typar, e.g. when implementing a
+// abstract generic method slot. But we later check the generalization
+// condition anyway, so we could get away with a non-rigid typar. This
+// would sort of be cleaner, though give errors later.
+let FreshenAndFixupTypars g m rigid fctps tinst tpsorig =
+ let tps = tpsorig |> List.map (FreshenTypar g rigid)
+ let renaming, tinst = FixupNewTypars m fctps tinst tpsorig tps
+ tps, renaming, tinst
+
+let FreshenTypeInst g m tpsorig =
+ FreshenAndFixupTypars g m TyparRigidity.Flexible [] [] tpsorig
+
+let FreshMethInst g m fctps tinst tpsorig =
+ FreshenAndFixupTypars g m TyparRigidity.Flexible fctps tinst tpsorig
+
+let FreshenTypars g m tpsorig =
+ match tpsorig with
+ | [] -> []
+ | _ ->
+ let _, _, tpTys = FreshenTypeInst g m tpsorig
+ tpTys
+
+let FreshenMethInfo m (minfo: MethInfo) =
+ let _, _, tpTys = FreshMethInst minfo.TcGlobals m (minfo.GetFormalTyparsOfDeclaringType m) minfo.DeclaringTypeInst minfo.FormalMethodTypars
+ tpTys
+
/// This must be called after fetching unqualified items that may need to be freshened
/// or have type instantiations
let ResolveUnqualifiedItem (ncenv: NameResolver) nenv m res =
@@ -1739,87 +1818,100 @@ let (|ValRefOfProp|_|) (pi: PropInfo) = pi.ArbitraryValRef
let (|ValRefOfMeth|_|) (mi: MethInfo) = mi.ArbitraryValRef
let (|ValRefOfEvent|_|) (evt: EventInfo) = evt.ArbitraryValRef
+[]
let rec (|RecordFieldUse|_|) (item: Item) =
match item with
- | Item.RecdField(RecdFieldInfo(_, RecdFieldRef(tcref, name))) -> Some (name, tcref)
- | Item.SetterArg(_, RecordFieldUse f) -> Some f
- | _ -> None
+ | Item.RecdField(RecdFieldInfo(_, RecdFieldRef(tcref, name))) -> ValueSome (name, tcref)
+ | Item.SetterArg(_, RecordFieldUse f) -> ValueSome f
+ | _ -> ValueNone
+[]
let (|UnionCaseFieldUse|_|) (item: Item) =
match item with
- | Item.UnionCaseField (uci, fieldIndex) -> Some (fieldIndex, uci.UnionCaseRef)
- | _ -> None
+ | Item.UnionCaseField (uci, fieldIndex) -> ValueSome (fieldIndex, uci.UnionCaseRef)
+ | _ -> ValueNone
+[]
let rec (|ILFieldUse|_|) (item: Item) =
match item with
- | Item.ILField finfo -> Some finfo
- | Item.SetterArg(_, ILFieldUse f) -> Some f
- | _ -> None
+ | Item.ILField finfo -> ValueSome finfo
+ | Item.SetterArg(_, ILFieldUse f) -> ValueSome f
+ | _ -> ValueNone
+[]
let rec (|PropertyUse|_|) (item: Item) =
match item with
- | Item.Property(info = pinfo :: _) -> Some pinfo
- | Item.SetterArg(_, PropertyUse pinfo) -> Some pinfo
- | _ -> None
+ | Item.Property(info = pinfo :: _) -> ValueSome pinfo
+ | Item.SetterArg(_, PropertyUse pinfo) -> ValueSome pinfo
+ | _ -> ValueNone
+[]
let rec (|FSharpPropertyUse|_|) (item: Item) =
match item with
- | Item.Property(info = [ValRefOfProp vref]) -> Some vref
- | Item.SetterArg(_, FSharpPropertyUse propDef) -> Some propDef
- | _ -> None
+ | Item.Property(info = [ValRefOfProp vref]) -> ValueSome vref
+ | Item.SetterArg(_, FSharpPropertyUse propDef) -> ValueSome propDef
+ | _ -> ValueNone
+[]
let (|MethodUse|_|) (item: Item) =
match item with
- | Item.MethodGroup(_, [minfo], _) -> Some minfo
- | _ -> None
+ | Item.MethodGroup(_, [minfo], _) -> ValueSome minfo
+ | _ -> ValueNone
+[]
let (|FSharpMethodUse|_|) (item: Item) =
match item with
- | Item.MethodGroup(_, [ValRefOfMeth vref], _) -> Some vref
- | Item.Value vref when vref.IsMember -> Some vref
- | _ -> None
+ | Item.MethodGroup(_, [ValRefOfMeth vref], _) -> ValueSome vref
+ | Item.Value vref when vref.IsMember -> ValueSome vref
+ | _ -> ValueNone
+[]
let (|EntityUse|_|) (item: Item) =
match item with
- | Item.UnqualifiedType (tcref :: _) -> Some tcref
- | Item.ExnCase tcref -> Some tcref
+ | Item.UnqualifiedType (tcref :: _) -> ValueSome tcref
+ | Item.ExnCase tcref -> ValueSome tcref
| Item.Types(_, [AbbrevOrAppTy tcref])
- | Item.DelegateCtor(AbbrevOrAppTy tcref) -> Some tcref
+ | Item.DelegateCtor(AbbrevOrAppTy tcref) -> ValueSome tcref
| Item.CtorGroup(_, ctor :: _) ->
match ctor.ApparentEnclosingType with
- | AbbrevOrAppTy tcref -> Some tcref
- | _ -> None
- | _ -> None
+ | AbbrevOrAppTy tcref -> ValueSome tcref
+ | _ -> ValueNone
+ | _ -> ValueNone
+[]
let (|EventUse|_|) (item: Item) =
match item with
- | Item.Event einfo -> Some einfo
- | _ -> None
+ | Item.Event einfo -> ValueSome einfo
+ | _ -> ValueNone
+[]
let (|FSharpEventUse|_|) (item: Item) =
match item with
- | Item.Event(ValRefOfEvent vref) -> Some vref
- | _ -> None
+ | Item.Event(ValRefOfEvent vref) -> ValueSome vref
+ | _ -> ValueNone
+[]
let (|UnionCaseUse|_|) (item: Item) =
match item with
- | Item.UnionCase(UnionCaseInfo(_, u1), _) -> Some u1
- | _ -> None
+ | Item.UnionCase(UnionCaseInfo(_, u1), _) -> ValueSome u1
+ | _ -> ValueNone
+[]
let (|ValUse|_|) (item: Item) =
match item with
| Item.Value vref
| FSharpPropertyUse vref
| FSharpMethodUse vref
| FSharpEventUse vref
- | Item.CustomBuilder(_, vref) -> Some vref
- | _ -> None
+ | Item.CustomBuilder(_, vref) -> ValueSome vref
+ | _ -> ValueNone
+[]
let (|ActivePatternCaseUse|_|) (item: Item) =
match item with
- | Item.ActivePatternCase(APElemRef(_, vref, idx, _)) -> Some (vref.SigRange, vref.DefinitionRange, idx)
- | Item.ActivePatternResult(ap, _, idx, _) -> Some (ap.Range, ap.Range, idx)
- | _ -> None
+ | Item.ActivePatternCase(APElemRef(_, vref, idx, _)) -> ValueSome (vref.SigRange, vref.DefinitionRange, idx)
+ | Item.ActivePatternResult(ap, _, idx, _) -> ValueSome (ap.Range, ap.Range, idx)
+ | _ -> ValueNone
let tyconRefDefnHash (_g: TcGlobals) (eref1: EntityRef) =
hash eref1.LogicalName
@@ -2840,9 +2932,10 @@ let private ResolveLongIdentInTyconRefs atMostOne (ncenv: NameResolver) nenv loo
// ResolveExprLongIdentInModuleOrNamespace
//-------------------------------------------------------------------------
+[]
let (|AccessibleEntityRef|_|) amap m ad (modref: ModuleOrNamespaceRef) mspec =
let eref = modref.NestedTyconRef mspec
- if IsEntityAccessible amap m ad eref then Some eref else None
+ if IsEntityAccessible amap m ad eref then ValueSome eref else ValueNone
let rec ResolveExprLongIdentInModuleOrNamespace (ncenv: NameResolver) nenv (typeNameResInfo: TypeNameResolutionInfo) ad resInfo depth m modref (mty: ModuleOrNamespaceType) (id: Ident) (rest: Ident list) =
// resInfo records the modules or namespaces actually relevant to a resolution
@@ -3082,8 +3175,9 @@ let rec ResolveExprLongIdentPrim sink (ncenv: NameResolver) first fullyQualified
|> CollectResults success
match tyconSearch () with
- | Result ((resInfo, tcref) :: _) ->
- let item = Item.Types(id.idText, [ generalizedTyconRef ncenv.g tcref ])
+ | Result((resInfo, tcref) :: _) ->
+ let _, _, tyargs = FreshenTypeInst ncenv.g m (tcref.Typars m)
+ let item = Item.Types(id.idText, [TType_app(tcref, tyargs, ncenv.g.knownWithoutNull)])
success (resInfo, item)
| _ ->
@@ -3447,9 +3541,12 @@ let ResolveTypeLongIdentInTyconRef sink (ncenv: NameResolver) nenv typeNameResIn
| id :: rest ->
ForceRaise (ResolveTypeLongIdentInTyconRefPrim ncenv typeNameResInfo ad ResolutionInfo.Empty PermitDirectReferenceToGeneratedType.No 0 m tcref id rest)
ResolutionInfo.SendEntityPathToSink(sink, ncenv, nenv, ItemOccurence.Use, ad, resInfo, ResultTyparChecker(fun () -> true))
- let item = Item.Types(tcref.DisplayName, [FreshenTycon ncenv m tcref])
- CallNameResolutionSink sink (rangeOfLid lid, nenv, item, emptyTyparInst, ItemOccurence.UseInType, ad)
- tcref
+
+ let _, tinst, tyargs = FreshenTypeInst ncenv.g m (tcref.Typars m)
+ let item = Item.Types(tcref.DisplayName, [TType_app(tcref, tyargs, ncenv.g.knownWithoutNull)])
+ CallNameResolutionSink sink (rangeOfLid lid, nenv, item, tinst, ItemOccurence.UseInType, ad)
+
+ tcref, tyargs
/// Create an UndefinedName error with details
let SuggestTypeLongIdentInModuleOrNamespace depth (modref: ModuleOrNamespaceRef) amap ad m (id: Ident) =
@@ -3593,7 +3690,6 @@ let rec ResolveTypeLongIdentPrim sink (ncenv: NameResolver) occurence first full
let r = AddResults searchSoFar (modulSearchFailed())
AtMostOneResult m2 (r |?> (fun tcrefs -> CheckForTypeLegitimacyAndMultipleGenericTypeAmbiguities (tcrefs, typeNameResInfo, genOk, m)))
-
/// Resolve a long identifier representing a type and report it
let ResolveTypeLongIdentAux sink (ncenv: NameResolver) occurence fullyQualified nenv ad (lid: Ident list) staticResInfo genOk =
let m = rangeOfLid lid
@@ -3608,15 +3704,20 @@ let ResolveTypeLongIdentAux sink (ncenv: NameResolver) occurence fullyQualified
match res with
| Result (resInfo, tcref) ->
ResolutionInfo.SendEntityPathToSink(sink, ncenv, nenv, ItemOccurence.UseInType, ad, resInfo, ResultTyparChecker(fun () -> true))
- let item = Item.Types(tcref.DisplayName, [FreshenTycon ncenv m tcref])
- CallNameResolutionSink sink (m, nenv, item, emptyTyparInst, occurence, ad)
- | _ -> ()
- res
+
+ let _, tinst, tyargs = FreshenTypeInst ncenv.g m (tcref.Typars m)
+ let item = Item.Types(tcref.DisplayName, [TType_app(tcref, tyargs, ncenv.g.knownWithoutNull)])
+ CallNameResolutionSink sink (m, nenv, item, tinst, occurence, ad)
+
+ Result(resInfo, tcref, tyargs)
+
+ | Exception exn ->
+ Exception exn
/// Resolve a long identifier representing a type and report it
let ResolveTypeLongIdent sink ncenv occurence fullyQualified nenv ad lid staticResInfo genOk =
let res = ResolveTypeLongIdentAux sink ncenv occurence fullyQualified nenv ad lid staticResInfo genOk
- (res |?> fun (resInfo, tcref) -> (resInfo.EnclosingTypeInst, tcref))
+ res |?> fun (resInfo, tcref, ttypes) -> (resInfo.EnclosingTypeInst, tcref, ttypes)
//-------------------------------------------------------------------------
// Resolve F#/IL "." syntax in records etc.
@@ -4088,11 +4189,12 @@ let ResolveLongIdentAsExprAndComputeRange (sink: TcResultsSink) (ncenv: NameReso
success (tinstEnclosing, item, itemRange, rest, afterResolution)
+[]
let (|NonOverridable|_|) namedItem =
match namedItem with
- | Item.MethodGroup(_, minfos, _) when minfos |> List.exists(fun minfo -> minfo.IsVirtual || minfo.IsAbstract) -> None
- | Item.Property(info = pinfos) when pinfos |> List.exists(fun pinfo -> pinfo.IsVirtualProperty) -> None
- | _ -> Some ()
+ | Item.MethodGroup(_, minfos, _) when minfos |> List.exists(fun minfo -> minfo.IsVirtual || minfo.IsAbstract) -> ValueNone
+ | Item.Property(info = pinfos) when pinfos |> List.exists(fun pinfo -> pinfo.IsVirtualProperty) -> ValueNone
+ | _ -> ValueSome ()
/// Called for 'expression.Bar' - for VS IntelliSense, we can filter out static members from method groups
/// Also called for 'GenericType.Bar' - for VS IntelliSense, we can filter out non-static members from method groups
diff --git a/src/Compiler/Checking/NameResolution.fsi b/src/Compiler/Checking/NameResolution.fsi
index 43cfdd12d5c..c80125f1862 100755
--- a/src/Compiler/Checking/NameResolution.fsi
+++ b/src/Compiler/Checking/NameResolution.fsi
@@ -676,6 +676,67 @@ exception internal UpperCaseIdentifierInPattern of range
/// Generate a new reference to a record field with a fresh type instantiation
val FreshenRecdFieldRef: NameResolver -> range -> RecdFieldRef -> RecdFieldInfo
+/// Create a type variable representing the use of a "_" in F# code
+val NewAnonTypar: TyparKind * range * TyparRigidity * TyparStaticReq * TyparDynamicReq -> Typar
+
+val NewNamedInferenceMeasureVar: range * TyparRigidity * TyparStaticReq * Ident -> Typar
+
+val NewNamedInferenceMeasureVar: range * TyparRigidity * TyparStaticReq * Ident -> Typar
+
+val NewInferenceMeasurePar: unit -> Typar
+
+/// Create an inference type variable
+val NewInferenceType: TcGlobals -> TType
+
+/// Create an inference type variable for the kind of a byref pointer
+val NewByRefKindInferenceType: TcGlobals -> range -> TType
+
+/// Create an inference type variable representing an error condition when checking an expression
+val NewErrorType: unit -> TType
+
+/// Create an inference type variable representing an error condition when checking a measure
+val NewErrorMeasure: unit -> Measure
+
+/// Create a list of inference type variables, one for each element in the input list
+val NewInferenceTypes: TcGlobals -> 'T list -> TType list
+
+/// Given a set of type parameters, make new inference type variables for
+/// each and ensure that the constraints on the new type variables are adjusted.
+///
+/// Returns the inference type variables as a list of types.
+val FreshenTypars: g: TcGlobals -> range -> Typars -> TType list
+
+/// Given a method, which may be generic, make new inference type variables for
+/// its generic parameters, and ensure that the constraints the new type variables are adjusted.
+///
+/// Returns the inference type variables as a list of types.
+val FreshenMethInfo: range -> MethInfo -> TType list
+
+/// Given a set of formal type parameters and their constraints, make new inference type variables for
+/// each and ensure that the constraints on the new type variables are adjusted to refer to these.
+///
+/// Returns
+/// 1. the new type parameters
+/// 2. the instantiation mapping old type parameters to inference variables
+/// 3. the inference type variables as a list of types.
+val FreshenAndFixupTypars:
+ g: TcGlobals ->
+ m: range ->
+ rigid: TyparRigidity ->
+ fctps: Typars ->
+ tinst: TType list ->
+ tpsorig: Typar list ->
+ Typar list * TyparInstantiation * TTypes
+
+/// Given a set of type parameters, make new inference type variables for
+/// each and ensure that the constraints on the new type variables are adjusted.
+///
+/// Returns
+/// 1. the new type parameters
+/// 2. the instantiation mapping old type parameters to inference variables
+/// 3. the inference type variables as a list of types.
+val FreshenTypeInst: g: TcGlobals -> m: range -> tpsorig: Typar list -> Typar list * TyparInstantiation * TTypes
+
/// Resolve a long identifier to a namespace, module.
val internal ResolveLongIdentAsModuleOrNamespace:
sink: TcResultsSink ->
@@ -733,7 +794,7 @@ val internal ResolveTypeLongIdentInTyconRef:
m: range ->
tcref: TyconRef ->
lid: Ident list ->
- TyconRef
+ TyconRef * TypeInst
/// Resolve a long identifier to a type definition
val internal ResolveTypeLongIdent:
@@ -746,7 +807,7 @@ val internal ResolveTypeLongIdent:
lid: Ident list ->
staticResInfo: TypeNameResolutionStaticArgsInfo ->
genOk: PermitDirectReferenceToGeneratedType ->
- ResultOrException
+ ResultOrException
/// Resolve a long identifier to a field
val internal ResolveField:
diff --git a/src/Compiler/Checking/PatternMatchCompilation.fs b/src/Compiler/Checking/PatternMatchCompilation.fs
index 3caacecb982..168bd86e60e 100644
--- a/src/Compiler/Checking/PatternMatchCompilation.fs
+++ b/src/Compiler/Checking/PatternMatchCompilation.fs
@@ -740,19 +740,22 @@ let ChooseInvestigationPointLeftToRight frontiers =
// This is an initial attempt to remove extra typetests/castclass for simple list pattern matching "match x with h :: t -> ... | [] -> ..."
// The problem with this technique is that it creates extra locals which inhibit the process of converting pattern matches into linear let bindings.
+[]
let (|ListConsDiscrim|_|) g = function
| (DecisionTreeTest.UnionCase (ucref, tinst))
(* check we can use a simple 'isinst' instruction *)
- when tyconRefEq g ucref.TyconRef g.list_tcr_canon & ucref.CaseName = "op_ColonColon" -> Some tinst
- | _ -> None
+ when tyconRefEq g ucref.TyconRef g.list_tcr_canon & ucref.CaseName = "op_ColonColon" -> ValueSome tinst
+ | _ -> ValueNone
+[]
let (|ListEmptyDiscrim|_|) g = function
| (DecisionTreeTest.UnionCase (ucref, tinst))
(* check we can use a simple 'isinst' instruction *)
- when tyconRefEq g ucref.TyconRef g.list_tcr_canon & ucref.CaseName = "op_Nil" -> Some tinst
- | _ -> None
+ when tyconRefEq g ucref.TyconRef g.list_tcr_canon & ucref.CaseName = "op_Nil" -> ValueSome tinst
+ | _ -> ValueNone
#endif
+[]
let (|ConstNeedsDefaultCase|_|) c =
match c with
| Const.Decimal _
@@ -767,8 +770,8 @@ let (|ConstNeedsDefaultCase|_|) c =
| Const.UInt64 _
| Const.IntPtr _
| Const.UIntPtr _
- | Const.Char _ -> Some ()
- | _ -> None
+ | Const.Char _ -> ValueSome ()
+ | _ -> ValueNone
/// Build a dtree, equivalent to: TDSwitch("expr", edges, default, m)
///
diff --git a/src/Compiler/Checking/QuotationTranslator.fs b/src/Compiler/Checking/QuotationTranslator.fs
index fa0d317ab95..8173c13755f 100644
--- a/src/Compiler/Checking/QuotationTranslator.fs
+++ b/src/Compiler/Checking/QuotationTranslator.fs
@@ -167,33 +167,37 @@ exception IgnoringPartOfQuotedTermWarning of string * range
let wfail e = raise (InvalidQuotedTerm e)
+[]
let (|ModuleValueOrMemberUse|_|) g expr =
let rec loop expr args =
match stripExpr expr with
| Expr.App (InnerExprPat(Expr.Val (vref, vFlags, _) as f), fty, tyargs, actualArgs, _m) when vref.IsMemberOrModuleBinding ->
- Some(vref, vFlags, f, fty, tyargs, actualArgs @ args)
+ ValueSome(vref, vFlags, f, fty, tyargs, actualArgs @ args)
| Expr.App (f, _fTy, [], actualArgs, _) ->
loop f (actualArgs @ args)
| Expr.Val (vref, vFlags, _m) as f when (match vref.TryDeclaringEntity with ParentNone -> false | _ -> true) ->
let fty = tyOfExpr g f
- Some(vref, vFlags, f, fty, [], args)
+ ValueSome(vref, vFlags, f, fty, [], args)
| _ ->
- None
+ ValueNone
loop expr []
+[]
let (|SimpleArrayLoopUpperBound|_|) expr =
match expr with
- | Expr.Op (TOp.ILAsm ([AI_sub], _), _, [Expr.Op (TOp.ILAsm ([I_ldlen; AI_conv ILBasicType.DT_I4], _), _, _, _); Expr.Const (Const.Int32 1, _, _) ], _) -> Some ()
- | _ -> None
+ | Expr.Op (TOp.ILAsm ([AI_sub], _), _, [Expr.Op (TOp.ILAsm ([I_ldlen; AI_conv ILBasicType.DT_I4], _), _, _, _); Expr.Const (Const.Int32 1, _, _) ], _) -> ValueSome ()
+ | _ -> ValueNone
+[]
let (|SimpleArrayLoopBody|_|) g expr =
match expr with
| Expr.Lambda (_, a, b, ([_] as args), DebugPoints (Expr.Let (TBind(forVarLoop, DebugPoints (Expr.Op (TOp.ILAsm ([I_ldelem_any(ILArrayShape [(Some 0, None)], _)], _), [elemTy], [arr; idx], m1), _), seqPoint), body, m2, freeVars), _), m, ty) ->
let body = Expr.Let (TBind(forVarLoop, mkCallArrayGet g m1 elemTy arr idx, seqPoint), body, m2, freeVars)
let expr = Expr.Lambda (newUnique(), a, b, args, body, m, ty)
- Some (arr, elemTy, expr)
- | _ -> None
+ ValueSome (arr, elemTy, expr)
+ | _ -> ValueNone
+[]
let (|ObjectInitializationCheck|_|) g expr =
// recognize "if this.init@ < 1 then failinit"
match expr with
@@ -207,8 +211,8 @@ let (|ObjectInitializationCheck|_|) g expr =
name.StartsWithOrdinal("init") &&
selfRef.IsMemberThisVal &&
valRefEq g failInitRef (ValRefForIntrinsic g.fail_init_info) &&
- isUnitTy g resultTy -> Some()
- | _ -> None
+ isUnitTy g resultTy -> ValueSome()
+ | _ -> ValueNone
let isSplice g vref = valRefEq g vref g.splice_expr_vref || valRefEq g vref g.splice_raw_expr_vref
diff --git a/src/Compiler/Checking/QuotationTranslator.fsi b/src/Compiler/Checking/QuotationTranslator.fsi
index 288a8e1e73d..25567f51a63 100644
--- a/src/Compiler/Checking/QuotationTranslator.fsi
+++ b/src/Compiler/Checking/QuotationTranslator.fsi
@@ -41,10 +41,17 @@ val ConvExprPublic: QuotationGenerationScope -> suppressWitnesses: bool -> Expr
val ConvReflectedDefinition:
QuotationGenerationScope -> string -> Val -> Expr -> QuotationPickler.MethodBaseData * QuotationPickler.ExprData
+[]
val (|ModuleValueOrMemberUse|_|):
- TcGlobals -> Expr -> (ValRef * ValUseFlag * Expr * TType * TypeInst * Expr list) option
+ TcGlobals -> Expr -> (ValRef * ValUseFlag * Expr * TType * TypeInst * Expr list) voption
+
+[]
+val (|SimpleArrayLoopUpperBound|_|): Expr -> unit voption
+
+[]
+val (|SimpleArrayLoopBody|_|): TcGlobals -> Expr -> (Expr * TType * Expr) voption
+
+[]
+val (|ObjectInitializationCheck|_|): TcGlobals -> Expr -> unit voption
-val (|SimpleArrayLoopUpperBound|_|): Expr -> unit option
-val (|SimpleArrayLoopBody|_|): TcGlobals -> Expr -> (Expr * TType * Expr) option
-val (|ObjectInitializationCheck|_|): TcGlobals -> Expr -> unit option
val isSplice: TcGlobals -> ValRef -> bool
diff --git a/src/Compiler/Checking/TailCallChecks.fs b/src/Compiler/Checking/TailCallChecks.fs
index a962252fe7f..cd8dfd2b77c 100644
--- a/src/Compiler/Checking/TailCallChecks.fs
+++ b/src/Compiler/Checking/TailCallChecks.fs
@@ -18,11 +18,12 @@ open FSharp.Compiler.TypeRelations
let PostInferenceChecksStackGuardDepth = GetEnvInteger "FSHARP_TailCallChecks" 50
+[]
let (|ValUseAtApp|_|) e =
match e with
| InnerExprPat(Expr.App(funcExpr = InnerExprPat(Expr.Val(valRef = vref; flags = valUseFlags))) | Expr.Val(
- valRef = vref; flags = valUseFlags)) -> Some(vref, valUseFlags)
- | _ -> None
+ valRef = vref; flags = valUseFlags)) -> ValueSome(vref, valUseFlags)
+ | _ -> ValueNone
type TailCallReturnType =
| MustReturnVoid // indicates "has unit return type and must return void"
@@ -67,8 +68,6 @@ type cenv =
amap: Import.ImportMap
- reportErrors: bool
-
/// Values in module that have been marked []
mustTailCall: Zset
}
@@ -139,12 +138,8 @@ let rec mkArgsForAppliedExpr isBaseCall argsl x =
| Expr.Op(TOp.Coerce, _, [ f ], _) -> mkArgsForAppliedExpr isBaseCall argsl f
| _ -> []
-/// Check an expression, where the expression is in a position where byrefs can be generated
-let rec CheckExprNoByrefs cenv (tailCall: TailCall) expr =
- CheckExpr cenv expr PermitByRefExpr.No tailCall
-
/// Check an expression, warn if it's attributed with TailCall but our analysis concludes it's not a valid tail call
-and CheckForNonTailRecCall (cenv: cenv) expr (tailCall: TailCall) =
+let CheckForNonTailRecCall (cenv: cenv) expr (tailCall: TailCall) =
let g = cenv.g
let expr = stripExpr expr
let expr = stripDebugPoints expr
@@ -152,68 +147,70 @@ and CheckForNonTailRecCall (cenv: cenv) expr (tailCall: TailCall) =
match expr with
| Expr.App(f, _fty, _tyargs, argsl, m) ->
- if cenv.reportErrors then
- if cenv.g.langVersion.SupportsFeature LanguageFeature.WarningWhenTailRecAttributeButNonTailRecUsage then
- match f with
- | ValUseAtApp(vref, valUseFlags) when cenv.mustTailCall.Contains vref.Deref ->
-
- let canTailCall =
- match tailCall with
- | TailCall.No -> // an upper level has already decided that this is not in a tailcall position
- false
- | TailCall.Yes returnType ->
- if vref.IsMemberOrModuleBinding && vref.ValReprInfo.IsSome then
- let topValInfo = vref.ValReprInfo.Value
-
- let nowArgs, laterArgs =
- let _, curriedArgInfos, _, _ =
- GetValReprTypeInFSharpForm cenv.g topValInfo vref.Type m
-
- if argsl.Length >= curriedArgInfos.Length then
- (List.splitAfter curriedArgInfos.Length argsl)
- else
- ([], argsl)
-
- let numEnclosingTypars = CountEnclosingTyparsOfActualParentOfVal vref.Deref
-
- let _, _, _, returnTy, _ =
- GetValReprTypeInCompiledForm g topValInfo numEnclosingTypars vref.Type m
-
- let _, _, isNewObj, isSuperInit, isSelfInit, _, _, _ =
- GetMemberCallInfo cenv.g (vref, valUseFlags)
-
- let isCCall =
- match valUseFlags with
- | PossibleConstrainedCall _ -> true
- | _ -> false
-
- let hasByrefArg = nowArgs |> List.exists (tyOfExpr cenv.g >> isByrefTy cenv.g)
-
- let mustGenerateUnitAfterCall =
- (Option.isNone returnTy && returnType <> TailCallReturnType.MustReturnVoid)
-
- let noTailCallBlockers =
- not isNewObj
- && not isSuperInit
- && not isSelfInit
- && not mustGenerateUnitAfterCall
- && isNil laterArgs
- && not (IsValRefIsDllImport cenv.g vref)
- && not isCCall
- && not hasByrefArg
-
- noTailCallBlockers // blockers that will prevent the IL level from emmiting a tail instruction
+ match f with
+ | ValUseAtApp(vref, valUseFlags) when cenv.mustTailCall.Contains vref.Deref ->
+
+ let canTailCall =
+ match tailCall with
+ | TailCall.No -> // an upper level has already decided that this is not in a tailcall position
+ false
+ | TailCall.Yes returnType ->
+ if vref.IsMemberOrModuleBinding && vref.ValReprInfo.IsSome then
+ let topValInfo = vref.ValReprInfo.Value
+
+ let nowArgs, laterArgs =
+ let _, curriedArgInfos, _, _ =
+ GetValReprTypeInFSharpForm cenv.g topValInfo vref.Type m
+
+ if argsl.Length >= curriedArgInfos.Length then
+ (List.splitAfter curriedArgInfos.Length argsl)
else
- true
+ ([], argsl)
- // warn if we call inside of recursive scope in non-tail-call manner/with tail blockers. See
- // ``Warn successfully in match clause``
- // ``Warn for byref parameters``
- if not canTailCall then
- warning (Error(FSComp.SR.chkNotTailRecursive vref.DisplayName, m))
- | _ -> ()
+ let numEnclosingTypars = CountEnclosingTyparsOfActualParentOfVal vref.Deref
+
+ let _, _, _, returnTy, _ =
+ GetValReprTypeInCompiledForm g topValInfo numEnclosingTypars vref.Type m
+
+ let _, _, isNewObj, isSuperInit, isSelfInit, _, _, _ =
+ GetMemberCallInfo cenv.g (vref, valUseFlags)
+
+ let isCCall =
+ match valUseFlags with
+ | PossibleConstrainedCall _ -> true
+ | _ -> false
+
+ let hasByrefArg = nowArgs |> List.exists (tyOfExpr cenv.g >> isByrefTy cenv.g)
+
+ let mustGenerateUnitAfterCall =
+ (Option.isNone returnTy && returnType <> TailCallReturnType.MustReturnVoid)
+
+ let noTailCallBlockers =
+ not isNewObj
+ && not isSuperInit
+ && not isSelfInit
+ && not mustGenerateUnitAfterCall
+ && isNil laterArgs
+ && not (IsValRefIsDllImport cenv.g vref)
+ && not isCCall
+ && not hasByrefArg
+
+ noTailCallBlockers // blockers that will prevent the IL level from emmiting a tail instruction
+ else
+ true
+
+ // warn if we call inside of recursive scope in non-tail-call manner/with tail blockers. See
+ // ``Warn successfully in match clause``
+ // ``Warn for byref parameters``
+ if not canTailCall then
+ warning (Error(FSComp.SR.chkNotTailRecursive vref.DisplayName, m))
+ | _ -> ()
| _ -> ()
+/// Check an expression, where the expression is in a position where byrefs can be generated
+let rec CheckExprNoByrefs cenv (tailCall: TailCall) expr =
+ CheckExpr cenv expr PermitByRefExpr.No tailCall
+
/// Check call arguments, including the return argument.
and CheckCall cenv args ctxts (tailCall: TailCall) =
// detect CPS-like expressions
@@ -729,10 +726,7 @@ and CheckBindings cenv binds =
let CheckModuleBinding cenv (isRec: bool) (TBind _ as bind) =
// warn for non-rec functions which have the attribute
- if
- cenv.reportErrors
- && cenv.g.langVersion.SupportsFeature LanguageFeature.WarningWhenTailCallAttrOnNonRec
- then
+ if cenv.g.langVersion.SupportsFeature LanguageFeature.WarningWhenTailCallAttrOnNonRec then
let isNotAFunction =
match bind.Var.ValReprInfo with
| Some info -> info.HasNoArgs
@@ -841,14 +835,17 @@ and CheckModuleSpec cenv isRec mbind =
| ModuleOrNamespaceBinding.Module(_mspec, rhs) -> CheckDefnInModule cenv rhs
-let CheckImplFile (g, amap, reportErrors, implFileContents) =
- let cenv =
- {
- g = g
- reportErrors = reportErrors
- stackGuard = StackGuard(PostInferenceChecksStackGuardDepth, "CheckImplFile")
- amap = amap
- mustTailCall = Zset.empty valOrder
- }
-
- CheckDefnInModule cenv implFileContents
+let CheckImplFile (g: TcGlobals, amap, reportErrors, implFileContents) =
+ if
+ reportErrors
+ && g.langVersion.SupportsFeature LanguageFeature.WarningWhenTailRecAttributeButNonTailRecUsage
+ then
+ let cenv =
+ {
+ g = g
+ stackGuard = StackGuard(PostInferenceChecksStackGuardDepth, "CheckImplFile")
+ amap = amap
+ mustTailCall = Zset.empty valOrder
+ }
+
+ CheckDefnInModule cenv implFileContents
diff --git a/src/Compiler/Checking/infos.fs b/src/Compiler/Checking/infos.fs
index d7fd858d957..8d249521aa0 100644
--- a/src/Compiler/Checking/infos.fs
+++ b/src/Compiler/Checking/infos.fs
@@ -2391,18 +2391,19 @@ let SettersOfPropInfos (pinfos: PropInfo list) = pinfos |> List.choose (fun pinf
let GettersOfPropInfos (pinfos: PropInfo list) = pinfos |> List.choose (fun pinfo -> if pinfo.HasGetter then Some(pinfo.GetterMethod, Some pinfo) else None)
+[]
let (|DifferentGetterAndSetter|_|) (pinfo: PropInfo) =
if not (pinfo.HasGetter && pinfo.HasSetter) then
- None
+ ValueNone
else
match pinfo.GetterMethod.ArbitraryValRef, pinfo.SetterMethod.ArbitraryValRef with
| Some getValRef, Some setValRef ->
if getValRef.Accessibility <> setValRef.Accessibility then
- Some (getValRef, setValRef)
+ ValueSome (getValRef, setValRef)
else
match getValRef.ValReprInfo with
| Some getValReprInfo when
// Getter has an index parameter
- getValReprInfo.TotalArgCount > 1 -> Some (getValRef, setValRef)
- | _ -> None
- | _ -> None
\ No newline at end of file
+ getValReprInfo.TotalArgCount > 1 -> ValueSome (getValRef, setValRef)
+ | _ -> ValueNone
+ | _ -> ValueNone
\ No newline at end of file
diff --git a/src/Compiler/Checking/infos.fsi b/src/Compiler/Checking/infos.fsi
index 7e1ee813ca8..a2b178d92ac 100644
--- a/src/Compiler/Checking/infos.fsi
+++ b/src/Compiler/Checking/infos.fsi
@@ -1101,4 +1101,5 @@ val SettersOfPropInfos: pinfos: PropInfo list -> (MethInfo * PropInfo option) li
val GettersOfPropInfos: pinfos: PropInfo list -> (MethInfo * PropInfo option) list
-val (|DifferentGetterAndSetter|_|): pinfo: PropInfo -> (ValRef * ValRef) option
+[]
+val (|DifferentGetterAndSetter|_|): pinfo: PropInfo -> (ValRef * ValRef) voption
diff --git a/src/Compiler/CodeGen/IlxGen.fs b/src/Compiler/CodeGen/IlxGen.fs
index d1d3c9f85c8..e301813edae 100644
--- a/src/Compiler/CodeGen/IlxGen.fs
+++ b/src/Compiler/CodeGen/IlxGen.fs
@@ -92,7 +92,7 @@ let ChooseParamNames fieldNamesAndTypes =
ilParamName, ilFieldName, ilPropType)
/// Approximation for purposes of optimization and giving a warning when compiling definition-only files as EXEs
-let rec CheckCodeDoesSomething (code: ILCode) =
+let CheckCodeDoesSomething (code: ILCode) =
code.Instrs
|> Array.exists (function
| AI_ldnull
@@ -476,7 +476,7 @@ let CompLocForPrivateImplementationDetails cloc =
}
/// Compute an ILTypeRef for a CompilationLocation
-let rec TypeRefForCompLoc cloc =
+let TypeRefForCompLoc cloc =
match cloc.Enclosing with
| [] -> mkILTyRef (cloc.Scope, TypeNameForPrivateImplementationDetails cloc)
| [ h ] ->
diff --git a/src/Compiler/Driver/GraphChecking/DependencyResolution.fs b/src/Compiler/Driver/GraphChecking/DependencyResolution.fs
index 9300385b483..11ba984ca51 100644
--- a/src/Compiler/Driver/GraphChecking/DependencyResolution.fs
+++ b/src/Compiler/Driver/GraphChecking/DependencyResolution.fs
@@ -1,7 +1,6 @@
module internal FSharp.Compiler.GraphChecking.DependencyResolution
open FSharp.Compiler.Syntax
-open Internal.Utilities.Library
/// Find a path from a starting TrieNode and return the end node or None
let queryTriePartial (trie: TrieNode) (path: LongIdentifier) : TrieNode option =
@@ -118,6 +117,20 @@ let rec processStateEntry (trie: TrieNode) (state: FileContentQueryState) (entry
FoundDependencies = foundDependencies
}
+ | ModuleName name ->
+ // We need to check if the module name is a hit in the Trie.
+ let state' =
+ let queryResult = queryTrie trie [ name ]
+ processIdentifier queryResult state
+
+ match state.OwnNamespace with
+ | None -> state'
+ | Some ns ->
+ // If there we currently have our own namespace,
+ // the combination of that namespace + module name should be checked as well.
+ let queryResult = queryTrieDual trie ns [ name ]
+ processIdentifier queryResult state'
+
///
/// For a given file's content, collect all missing ("ghost") file dependencies that the core resolution algorithm didn't return,
/// but are required to satisfy the type-checker.
diff --git a/src/Compiler/Driver/GraphChecking/FileContentMapping.fs b/src/Compiler/Driver/GraphChecking/FileContentMapping.fs
index 526a22ef099..a09b64a3584 100644
--- a/src/Compiler/Driver/GraphChecking/FileContentMapping.fs
+++ b/src/Compiler/Driver/GraphChecking/FileContentMapping.fs
@@ -18,6 +18,11 @@ let longIdentToPath (skipLast: bool) (longId: LongIdent) : LongIdentifier =
let synLongIdentToPath (skipLast: bool) (synLongIdent: SynLongIdent) =
longIdentToPath skipLast synLongIdent.LongIdent
+/// In some rare cases we are interested in the name of a single Ident.
+/// For example `nameof ModuleName` in expressions or patterns.
+let visitIdentAsPotentialModuleName (moduleNameIdent: Ident) =
+ FileContentEntry.ModuleName moduleNameIdent.idText
+
let visitSynLongIdent (lid: SynLongIdent) : FileContentEntry list = visitLongIdent lid.LongIdent
let visitLongIdent (lid: LongIdent) =
@@ -51,7 +56,7 @@ let visitSynModuleDecl (decl: SynModuleDecl) : FileContentEntry list =
| SynModuleDecl.NestedModule(moduleInfo = SynComponentInfo(longId = [ ident ]; attributes = attributes); decls = decls) ->
yield! visitSynAttributes attributes
yield FileContentEntry.NestedModule(ident.idText, List.collect visitSynModuleDecl decls)
- | SynModuleDecl.NestedModule _ -> failwith "A nested module cannot have multiple identifiers"
+ | SynModuleDecl.NestedModule _ -> () // A nested module cannot have multiple identifiers. This will already be a parse error, but we could be working with recovered syntax tree
| SynModuleDecl.Let(bindings = bindings) -> yield! List.collect visitBinding bindings
| SynModuleDecl.Types(typeDefns = typeDefns) -> yield! List.collect visitSynTypeDefn typeDefns
| SynModuleDecl.HashDirective _ -> ()
@@ -75,7 +80,7 @@ let visitSynModuleSigDecl (md: SynModuleSigDecl) =
| SynModuleSigDecl.NestedModule(moduleInfo = SynComponentInfo(longId = [ ident ]; attributes = attributes); moduleDecls = decls) ->
yield! visitSynAttributes attributes
yield FileContentEntry.NestedModule(ident.idText, List.collect visitSynModuleSigDecl decls)
- | SynModuleSigDecl.NestedModule _ -> failwith "A nested module cannot have multiple identifiers"
+ | SynModuleSigDecl.NestedModule _ -> () // A nested module cannot have multiple identifiers. This will already be a parse error, but we could be working with recovered syntax tree
| SynModuleSigDecl.ModuleAbbrev(longId = longId) -> yield! visitLongIdentForModuleAbbrev longId
| SynModuleSigDecl.Val(valSig, _) -> yield! visitSynValSig valSig
| SynModuleSigDecl.Types(types = types) -> yield! List.collect visitSynTypeDefnSig types
@@ -302,9 +307,28 @@ let visitSynTypeConstraint (tc: SynTypeConstraint) : FileContentEntry list =
| SynTypeConstraint.WhereTyparIsEnum(typeArgs = typeArgs) -> List.collect visitSynType typeArgs
| SynTypeConstraint.WhereTyparIsDelegate(typeArgs = typeArgs) -> List.collect visitSynType typeArgs
+[]
+let inline (|NameofIdent|_|) (ident: Ident) =
+ if ident.idText = "nameof" then ValueSome() else ValueNone
+
+/// Special case of `nameof Module` type of expression
+let (|NameofExpr|_|) (e: SynExpr) =
+ let rec stripParen (e: SynExpr) =
+ match e with
+ | SynExpr.Paren(expr = expr) -> stripParen expr
+ | _ -> e
+
+ match e with
+ | SynExpr.App(flag = ExprAtomicFlag.NonAtomic; isInfix = false; funcExpr = SynExpr.Ident NameofIdent; argExpr = moduleNameExpr) ->
+ match stripParen moduleNameExpr with
+ | SynExpr.Ident moduleNameIdent -> Some moduleNameIdent
+ | _ -> None
+ | _ -> None
+
let visitSynExpr (e: SynExpr) : FileContentEntry list =
let rec visit (e: SynExpr) (continuation: FileContentEntry list -> FileContentEntry list) : FileContentEntry list =
match e with
+ | NameofExpr moduleNameIdent -> continuation [ visitIdentAsPotentialModuleName moduleNameIdent ]
| SynExpr.Const _ -> continuation []
| SynExpr.Paren(expr = expr) -> visit expr continuation
| SynExpr.Quote(operator = operator; quotedExpr = quotedExpr) ->
@@ -389,7 +413,7 @@ let visitSynExpr (e: SynExpr) : FileContentEntry list =
| SynExpr.IfThenElse(ifExpr = ifExpr; thenExpr = thenExpr; elseExpr = elseExpr) ->
let continuations = List.map visit (ifExpr :: thenExpr :: Option.toList elseExpr)
Continuation.concatenate continuations continuation
- | SynExpr.Typar _ -> continuation []
+ | SynExpr.Typar _
| SynExpr.Ident _ -> continuation []
| SynExpr.LongIdent(longDotId = longDotId) -> continuation (visitSynLongIdent longDotId)
| SynExpr.LongIdentSet(longDotId, expr, _) -> visit expr (fun nodes -> visitSynLongIdent longDotId @ nodes |> continuation)
@@ -517,9 +541,29 @@ let visitSynExpr (e: SynExpr) : FileContentEntry list =
visit e id
+/// Special case of `| nameof Module ->` type of pattern
+let (|NameofPat|_|) (pat: SynPat) =
+ let rec stripPats p =
+ match p with
+ | SynPat.Paren(pat = pat) -> stripPats pat
+ | _ -> p
+
+ match pat with
+ | SynPat.LongIdent(longDotId = SynLongIdent(id = [ NameofIdent ]); typarDecls = None; argPats = SynArgPats.Pats [ moduleNamePat ]) ->
+ match stripPats moduleNamePat with
+ | SynPat.LongIdent(
+ longDotId = SynLongIdent.SynLongIdent(id = [ moduleNameIdent ]; dotRanges = []; trivia = [ None ])
+ extraId = None
+ typarDecls = None
+ argPats = SynArgPats.Pats []
+ accessibility = None) -> Some moduleNameIdent
+ | _ -> None
+ | _ -> None
+
let visitPat (p: SynPat) : FileContentEntry list =
let rec visit (p: SynPat) (continuation: FileContentEntry list -> FileContentEntry list) : FileContentEntry list =
match p with
+ | NameofPat moduleNameIdent -> continuation [ visitIdentAsPotentialModuleName moduleNameIdent ]
| SynPat.Paren(pat = pat) -> visit pat continuation
| SynPat.Typed(pat = pat; targetType = t) -> visit pat (fun nodes -> nodes @ visitSynType t)
| SynPat.Const _ -> continuation []
diff --git a/src/Compiler/Driver/GraphChecking/Graph.fs b/src/Compiler/Driver/GraphChecking/Graph.fs
index dd51ea190a2..dbe4c6b6cc7 100644
--- a/src/Compiler/Driver/GraphChecking/Graph.fs
+++ b/src/Compiler/Driver/GraphChecking/Graph.fs
@@ -27,26 +27,43 @@ module internal Graph =
|> Array.map (fun (KeyValue(k, v)) -> k, v)
|> readOnlyDict
- let transitive<'Node when 'Node: equality> (graph: Graph<'Node>) : Graph<'Node> =
- /// Find transitive dependencies of a single node.
- let transitiveDeps (node: 'Node) =
- let visited = HashSet<'Node>()
+ let nodes (graph: Graph<'Node>) : Set<'Node> =
+ graph.Values |> Seq.collect id |> Seq.append graph.Keys |> Set
+
+ /// Find transitive dependencies of a single node.
+ let transitiveDeps (node: 'Node) (graph: Graph<'Node>) =
+ let visited = HashSet<'Node>()
- let rec dfs (node: 'Node) =
- graph[node]
- // Add direct dependencies.
- // Use HashSet.Add return value semantics to filter out those that were added previously.
- |> Array.filter visited.Add
- |> Array.iter dfs
+ let rec dfs (node: 'Node) =
+ graph[node]
+ // Add direct dependencies.
+ // Use HashSet.Add return value semantics to filter out those that were added previously.
+ |> Array.filter visited.Add
+ |> Array.iter dfs
- dfs node
- visited |> Seq.toArray
+ dfs node
+ visited |> Seq.toArray
+ let transitive<'Node when 'Node: equality> (graph: Graph<'Node>) : Graph<'Node> =
graph.Keys
|> Seq.toArray
- |> Array.Parallel.map (fun node -> node, transitiveDeps node)
+ |> Array.Parallel.map (fun node -> node, graph |> transitiveDeps node)
|> readOnlyDict
+ // TODO: optimize
+ /// Get subgraph of the given graph that contains only nodes that are reachable from the given node.
+ let subGraphFor node graph =
+ let allDeps = graph |> transitiveDeps node
+ let relevant n = n = node || allDeps |> Array.contains n
+
+ graph
+ |> Seq.choose (fun (KeyValue(src, deps)) ->
+ if relevant src then
+ Some(src, deps |> Array.filter relevant)
+ else
+ None)
+ |> make
+
/// Create a reverse of the graph
let reverse (originalGraph: Graph<'Node>) : Graph<'Node> =
originalGraph
@@ -69,7 +86,7 @@ module internal Graph =
let print (graph: Graph<'Node>) : unit =
printCustom graph (fun node -> node.ToString())
- let serialiseToMermaid path (graph: Graph) =
+ let serialiseToMermaid (graph: Graph) =
let sb = StringBuilder()
let appendLine (line: string) = sb.AppendLine(line) |> ignore
@@ -84,8 +101,10 @@ module internal Graph =
appendLine $" %i{idx} --> %i{depIdx}"
appendLine "```"
+ sb.ToString()
+ let writeMermaidToFile path (graph: Graph) =
use out =
FileSystem.OpenFileForWriteShim(path, fileMode = System.IO.FileMode.Create)
- out.WriteAllText(sb.ToString())
+ graph |> serialiseToMermaid |> out.WriteAllText
diff --git a/src/Compiler/Driver/GraphChecking/Graph.fsi b/src/Compiler/Driver/GraphChecking/Graph.fsi
index 95542470d8a..a93e429d2fe 100644
--- a/src/Compiler/Driver/GraphChecking/Graph.fsi
+++ b/src/Compiler/Driver/GraphChecking/Graph.fsi
@@ -10,12 +10,18 @@ module internal Graph =
/// Build the graph.
val make: nodeDeps: seq<'Node * 'Node array> -> Graph<'Node> when 'Node: equality
val map<'T, 'U when 'U: equality> : f: ('T -> 'U) -> graph: Graph<'T> -> Graph<'U>
+ /// Get all nodes of the graph.
+ val nodes: graph: Graph<'Node> -> Set<'Node>
/// Create a transitive closure of the graph in O(n^2) time (but parallelize it).
/// The resulting graph contains edge A -> C iff the input graph contains a (directed) non-zero length path from A to C.
val transitive<'Node when 'Node: equality> : graph: Graph<'Node> -> Graph<'Node>
+ /// Get a sub-graph of the graph containing only the nodes reachable from the given node.
+ val subGraphFor: node: 'Node -> graph: Graph<'Node> -> Graph<'Node> when 'Node: equality
/// Create a reverse of the graph.
val reverse<'Node when 'Node: equality> : originalGraph: Graph<'Node> -> Graph<'Node>
/// Print the contents of the graph to the standard output.
val print: graph: Graph<'Node> -> unit
+ /// Create a simple Mermaid graph
+ val serialiseToMermaid: graph: Graph -> string
/// Create a simple Mermaid graph and save it under the path specified.
- val serialiseToMermaid: path: string -> graph: Graph -> unit
+ val writeMermaidToFile: path: string -> graph: Graph -> unit
diff --git a/src/Compiler/Driver/GraphChecking/GraphProcessing.fs b/src/Compiler/Driver/GraphChecking/GraphProcessing.fs
index 47993e00862..afe491b4b74 100644
--- a/src/Compiler/Driver/GraphChecking/GraphProcessing.fs
+++ b/src/Compiler/Driver/GraphChecking/GraphProcessing.fs
@@ -1,6 +1,9 @@
module internal FSharp.Compiler.GraphChecking.GraphProcessing
open System.Threading
+open FSharp.Compiler.GraphChecking
+open System.Threading.Tasks
+open System
/// Information about the node in a graph, describing its relation with other nodes.
type NodeInfo<'Item> =
@@ -32,6 +35,9 @@ type ProcessedNode<'Item, 'Result> =
Result: 'Result
}
+type GraphProcessingException(msg, ex: System.Exception) =
+ inherit exn(msg, ex)
+
let processGraph<'Item, 'Result when 'Item: equality and 'Item: comparison>
(graph: Graph<'Item>)
(work: ('Item -> ProcessedNode<'Item, 'Result>) -> NodeInfo<'Item> -> 'Result)
@@ -150,7 +156,7 @@ let processGraph<'Item, 'Result when 'Item: equality and 'Item: comparison>
// If we stopped early due to an exception, reraise it.
match getExn () with
| None -> ()
- | Some(item, ex) -> raise (System.Exception($"Encountered exception when processing item '{item}'", ex))
+ | Some(item, ex) -> raise (GraphProcessingException($"Encountered exception when processing item '{item}'", ex))
// All calculations succeeded - extract the results and sort in input order.
nodes.Values
@@ -162,3 +168,131 @@ let processGraph<'Item, 'Result when 'Item: equality and 'Item: comparison>
node.Info.Item, result)
|> Seq.sortBy fst
|> Seq.toArray
+
+let processGraphAsync<'Item, 'Result when 'Item: equality and 'Item: comparison>
+ (graph: Graph<'Item>)
+ (work: ('Item -> ProcessedNode<'Item, 'Result>) -> NodeInfo<'Item> -> Async<'Result>)
+ : Async<('Item * 'Result)[]> =
+ async {
+ let transitiveDeps = graph |> Graph.transitive
+ let dependants = graph |> Graph.reverse
+ // Cancellation source used to signal either an exception in one of the items or end of processing.
+ let! parentCt = Async.CancellationToken
+ use localCts = new CancellationTokenSource()
+
+ let completionSignal = TaskCompletionSource()
+
+ use _ = parentCt.Register(fun () -> completionSignal.TrySetCanceled() |> ignore)
+
+ use cts = CancellationTokenSource.CreateLinkedTokenSource(parentCt, localCts.Token)
+
+ let makeNode (item: 'Item) : GraphNode<'Item, 'Result> =
+ let info =
+ let exists = graph.ContainsKey item
+
+ if
+ not exists
+ || not (transitiveDeps.ContainsKey item)
+ || not (dependants.ContainsKey item)
+ then
+ printfn $"Unexpected inconsistent state of the graph for item '{item}'"
+
+ {
+ Item = item
+ Deps = graph[item]
+ TransitiveDeps = transitiveDeps[item]
+ Dependants = dependants[item]
+ }
+
+ {
+ Info = info
+ Result = None
+ ProcessedDepsCount = IncrementableInt(0)
+ }
+
+ let nodes = graph.Keys |> Seq.map (fun item -> item, makeNode item) |> readOnlyDict
+
+ let lookupMany items =
+ items |> Array.map (fun item -> nodes[item])
+
+ let leaves =
+ nodes.Values |> Seq.filter (fun n -> n.Info.Deps.Length = 0) |> Seq.toArray
+
+ let getItemPublicNode item =
+ let node = nodes[item]
+
+ {
+ ProcessedNode.Info = node.Info
+ ProcessedNode.Result =
+ node.Result
+ |> Option.defaultWith (fun () -> failwith $"Results for item '{node.Info.Item}' are not yet available")
+ }
+
+ let processedCount = IncrementableInt(0)
+
+ let raiseExn (item, ex: exn) =
+ localCts.Cancel()
+
+ match ex with
+ | :? OperationCanceledException -> completionSignal.TrySetCanceled()
+ | _ ->
+ completionSignal.TrySetException(
+ GraphProcessingException($"[*] Encountered exception when processing item '{item}': {ex.Message}", ex)
+ )
+ |> ignore
+
+ let incrementProcessedNodesCount () =
+ if processedCount.Increment() = nodes.Count then
+ completionSignal.TrySetResult() |> ignore
+
+ let rec queueNode node =
+ Async.Start(
+ async {
+ let! res = processNode node |> Async.Catch
+
+ match res with
+ | Choice1Of2() -> ()
+ | Choice2Of2 ex -> raiseExn (node.Info.Item, ex)
+ },
+ cts.Token
+ )
+
+ and processNode (node: GraphNode<'Item, 'Result>) : Async =
+ async {
+
+ let info = node.Info
+
+ let! singleRes = work getItemPublicNode info
+ node.Result <- Some singleRes
+
+ let unblockedDependants =
+ node.Info.Dependants
+ |> lookupMany
+ // For every dependant, increment its number of processed dependencies,
+ // and filter dependants which now have all dependencies processed (but didn't before).
+ |> Array.filter (fun dependant ->
+ let pdc = dependant.ProcessedDepsCount.Increment()
+ // Note: We cannot read 'dependant.ProcessedDepsCount' again to avoid returning the same item multiple times.
+ pdc = dependant.Info.Deps.Length)
+
+ unblockedDependants |> Array.iter queueNode
+ incrementProcessedNodesCount ()
+ }
+
+ leaves |> Array.iter queueNode
+
+ // Wait for end of processing, an exception, or an external cancellation request.
+ do! completionSignal.Task |> Async.AwaitTask
+
+ // All calculations succeeded - extract the results and sort in input order.
+ return
+ nodes.Values
+ |> Seq.map (fun node ->
+ let result =
+ node.Result
+ |> Option.defaultWith (fun () -> failwith $"Unexpected lack of result for item '{node.Info.Item}'")
+
+ node.Info.Item, result)
+ |> Seq.sortBy fst
+ |> Seq.toArray
+ }
diff --git a/src/Compiler/Driver/GraphChecking/GraphProcessing.fsi b/src/Compiler/Driver/GraphChecking/GraphProcessing.fsi
index cb9a95a59f8..585daa52fd7 100644
--- a/src/Compiler/Driver/GraphChecking/GraphProcessing.fsi
+++ b/src/Compiler/Driver/GraphChecking/GraphProcessing.fsi
@@ -15,6 +15,10 @@ type ProcessedNode<'Item, 'Result> =
{ Info: NodeInfo<'Item>
Result: 'Result }
+type GraphProcessingException =
+ inherit exn
+ new: msg: string * ex: System.Exception -> GraphProcessingException
+
///
/// A generic method to generate results for a graph of work items in parallel.
/// Processes leaves first, and after each node has been processed, schedules any now unblocked dependants.
@@ -33,3 +37,8 @@ val processGraph<'Item, 'Result when 'Item: equality and 'Item: comparison> :
work: (('Item -> ProcessedNode<'Item, 'Result>) -> NodeInfo<'Item> -> 'Result) ->
parentCt: CancellationToken ->
('Item * 'Result)[]
+
+val processGraphAsync<'Item, 'Result when 'Item: equality and 'Item: comparison> :
+ graph: Graph<'Item> ->
+ work: (('Item -> ProcessedNode<'Item, 'Result>) -> NodeInfo<'Item> -> Async<'Result>) ->
+ Async<('Item * 'Result)[]>
diff --git a/src/Compiler/Driver/GraphChecking/Types.fs b/src/Compiler/Driver/GraphChecking/Types.fs
index 00538b6e599..c667a573f69 100644
--- a/src/Compiler/Driver/GraphChecking/Types.fs
+++ b/src/Compiler/Driver/GraphChecking/Types.fs
@@ -73,6 +73,9 @@ type internal FileContentEntry =
/// Being explicit about nested modules allows for easier reasoning what namespaces (paths) are open.
/// We can scope an `OpenStatement` to the everything that is happening inside the nested module.
| NestedModule of name: string * nestedContent: FileContentEntry list
+ /// A single identifier that could be the name of a module.
+ /// Example use-case: `let x = nameof Foo` where `Foo` is a module.
+ | ModuleName of name: Identifier
type internal FileContent =
{
diff --git a/src/Compiler/Driver/GraphChecking/Types.fsi b/src/Compiler/Driver/GraphChecking/Types.fsi
index 468ef65889c..096719b6be7 100644
--- a/src/Compiler/Driver/GraphChecking/Types.fsi
+++ b/src/Compiler/Driver/GraphChecking/Types.fsi
@@ -67,6 +67,9 @@ type internal FileContentEntry =
/// Being explicit about nested modules allows for easier reasoning what namespaces (paths) are open.
/// For example we can limit the scope of an `OpenStatement` to symbols defined inside the nested module.
| NestedModule of name: string * nestedContent: FileContentEntry list
+ /// A single identifier that could be the name of a module.
+ /// Example use-case: `let x = nameof Foo` where `Foo` is a module.
+ | ModuleName of name: Identifier
/// File identifiers and its content extract for dependency resolution
type internal FileContent =
diff --git a/src/Compiler/Driver/ParseAndCheckInputs.fs b/src/Compiler/Driver/ParseAndCheckInputs.fs
index a9fc59b66d3..5a23c95ca7b 100644
--- a/src/Compiler/Driver/ParseAndCheckInputs.fs
+++ b/src/Compiler/Driver/ParseAndCheckInputs.fs
@@ -1483,7 +1483,7 @@ let CheckOneInputWithCallback
prefixPathOpt,
tcSink,
tcState: TcState,
- inp: ParsedInput,
+ input: ParsedInput,
_skipImplIfSigExists: bool):
(unit -> bool) * TcConfig * TcImports * TcGlobals * LongIdent option * TcResultsSink * TcState * ParsedInput * bool)
: Cancellable> =
@@ -1491,7 +1491,7 @@ let CheckOneInputWithCallback
try
CheckSimulateException tcConfig
- let m = inp.Range
+ let m = input.Range
let amap = tcImports.GetImportMap()
let conditionalDefines =
@@ -1500,7 +1500,7 @@ let CheckOneInputWithCallback
else
Some tcConfig.conditionalDefines
- match inp with
+ match input with
| ParsedInput.SigFile file ->
let qualNameOfFile = file.QualifiedName
@@ -1740,6 +1740,43 @@ module private TypeCheckingGraphProcessing =
finalFileResults, state
+let TransformDependencyGraph (graph: Graph, filePairs: FilePairMap) =
+ let mkArtificialImplFile n = NodeToTypeCheck.ArtificialImplFile n
+ let mkPhysicalFile n = NodeToTypeCheck.PhysicalFile n
+
+ /// Map any signature dependencies to the ArtificialImplFile counterparts,
+ /// unless the signature dependency is the backing file of the current (implementation) file.
+ let mapDependencies idx deps =
+ Array.map
+ (fun dep ->
+ if filePairs.IsSignature dep then
+ let implIdx = filePairs.GetImplementationIndex dep
+
+ if implIdx = idx then
+ // This is the matching signature for the implementation.
+ // Retain the direct dependency onto the signature file.
+ mkPhysicalFile dep
+ else
+ mkArtificialImplFile dep
+ else
+ mkPhysicalFile dep)
+ deps
+
+ // Transform the graph to include ArtificialImplFile nodes when necessary.
+ graph
+ |> Seq.collect (fun (KeyValue(fileIdx, deps)) ->
+ if filePairs.IsSignature fileIdx then
+ // Add an additional ArtificialImplFile node for the signature file.
+ [|
+ // Mark the current file as physical and map the dependencies.
+ mkPhysicalFile fileIdx, mapDependencies fileIdx deps
+ // Introduce a new node that depends on the signature.
+ mkArtificialImplFile fileIdx, [| mkPhysicalFile fileIdx |]
+ |]
+ else
+ [| mkPhysicalFile fileIdx, mapDependencies fileIdx deps |])
+ |> Graph.make
+
/// Constructs a file dependency graph and type-checks the files in parallel where possible.
let CheckMultipleInputsUsingGraphMode
((ctok, checkForErrors, tcConfig: TcConfig, tcImports: TcImports, tcGlobals, prefixPathOpt, tcState, eagerFormat, inputs):
@@ -1768,42 +1805,7 @@ let CheckMultipleInputsUsingGraphMode
let filePairs = FilePairMap(sourceFiles)
let graph, trie = DependencyResolution.mkGraph filePairs sourceFiles
- let nodeGraph =
- let mkArtificialImplFile n = NodeToTypeCheck.ArtificialImplFile n
- let mkPhysicalFile n = NodeToTypeCheck.PhysicalFile n
-
- /// Map any signature dependencies to the ArtificialImplFile counterparts,
- /// unless the signature dependency is the backing file of the current (implementation) file.
- let mapDependencies idx deps =
- Array.map
- (fun dep ->
- if filePairs.IsSignature dep then
- let implIdx = filePairs.GetImplementationIndex dep
-
- if implIdx = idx then
- // This is the matching signature for the implementation.
- // Retain the direct dependency onto the signature file.
- mkPhysicalFile dep
- else
- mkArtificialImplFile dep
- else
- mkPhysicalFile dep)
- deps
-
- // Transform the graph to include ArtificialImplFile nodes when necessary.
- graph
- |> Seq.collect (fun (KeyValue(fileIdx, deps)) ->
- if filePairs.IsSignature fileIdx then
- // Add an additional ArtificialImplFile node for the signature file.
- [|
- // Mark the current file as physical and map the dependencies.
- mkPhysicalFile fileIdx, mapDependencies fileIdx deps
- // Introduce a new node that depends on the signature.
- mkArtificialImplFile fileIdx, [| mkPhysicalFile fileIdx |]
- |]
- else
- [| mkPhysicalFile fileIdx, mapDependencies fileIdx deps |])
- |> Graph.make
+ let nodeGraph = TransformDependencyGraph(graph, filePairs)
// Persist the graph to a Mermaid diagram if specified.
if tcConfig.typeCheckingConfig.DumpGraph then
@@ -1823,7 +1825,7 @@ let CheckMultipleInputsUsingGraphMode
.TrimStart([| '\\'; '/' |])
(idx, friendlyFileName))
- |> Graph.serialiseToMermaid graphFile)
+ |> Graph.writeMermaidToFile graphFile)
let _ = ctok // TODO Use it
let diagnosticsLogger = DiagnosticsThreadStatics.DiagnosticsLogger
diff --git a/src/Compiler/Driver/ParseAndCheckInputs.fsi b/src/Compiler/Driver/ParseAndCheckInputs.fsi
index 745afa51be4..875be616a8e 100644
--- a/src/Compiler/Driver/ParseAndCheckInputs.fsi
+++ b/src/Compiler/Driver/ParseAndCheckInputs.fsi
@@ -13,12 +13,44 @@ open FSharp.Compiler.CompilerImports
open FSharp.Compiler.Diagnostics
open FSharp.Compiler.DependencyManager
open FSharp.Compiler.DiagnosticsLogger
+open FSharp.Compiler.GraphChecking
+open FSharp.Compiler.NameResolution
open FSharp.Compiler.Syntax
open FSharp.Compiler.TcGlobals
open FSharp.Compiler.Text
open FSharp.Compiler.TypedTree
open FSharp.Compiler.UnicodeLexing
+/// Auxiliary type for re-using signature information in TcEnvFromImpls.
+///
+/// TcState has two typing environments: TcEnvFromSignatures && TcEnvFromImpls
+/// When type checking a file, depending on the type (implementation or signature), it will use one of these typing environments (TcEnv).
+/// Checking a file will populate the respective TcEnv.
+///
+/// When a file has a dependencies, the information of the signature file in case a pair (implementation file backed by a signature) will suffice to type-check that file.
+/// Example: if `B.fs` has a dependency on `A`, the information of `A.fsi` is enough for `B.fs` to type-check, on condition that information is available in the TcEnvFromImpls.
+/// We introduce a special ArtificialImplFile node in the graph to satisfy this. `B.fs -> [ A.fsi ]` becomes `B.fs -> [ ArtificialImplFile A ].
+/// The `ArtificialImplFile A` node will duplicate the signature information which A.fsi provided earlier.
+/// Processing a `ArtificialImplFile` node will add the information from the TcEnvFromSignatures to the TcEnvFromImpls.
+/// This means `A` will be known in both TcEnvs and therefor `B.fs` can be type-checked.
+/// By doing this, we can speed up the graph processing as type checking a signature file is less expensive than its implementation counterpart.
+///
+/// When we need to actually type-check an implementation file backed by a signature, we cannot have the duplicate information of the signature file present in TcEnvFromImpls.
+/// Example `A.fs -> [ A.fsi ]`. An implementation file always depends on its signature.
+/// Type-checking `A.fs` will add the actual information to TcEnvFromImpls and we do not depend on the `ArtificialImplFile A` for `A.fs`.
+///
+/// In order to deal correctly with the `ArtificialImplFile` logic, we need to transform the resolved graph to contain the additional pair nodes.
+/// After we have type-checked the graph, we exclude the ArtificialImplFile nodes as they are not actual physical files in our project.
+[]
+type NodeToTypeCheck =
+ /// A real physical file in the current project.
+ /// This can be either an implementation or a signature file.
+ | PhysicalFile of fileIndex: FileIndex
+ /// An artificial node that will add the earlier processed signature information to the TcEnvFromImpls.
+ /// Dependants on this type of node will perceive that a file is known in both TcEnvFromSignatures and TcEnvFromImpls.
+ /// Even though the actual implementation file was not type-checked.
+ | ArtificialImplFile of signatureFileIndex: FileIndex
+
val IsScript: string -> bool
val ComputeQualifiedNameOfFileFromUniquePath: range * string list -> QualifiedNameOfFile
@@ -131,6 +163,8 @@ type TcState =
member CreatesGeneratedProvidedTypes: bool
+type PartialResult = TcEnv * TopAttribs * CheckedImplFile option * ModuleOrNamespaceType
+
/// Get the initial type checking state for a set of inputs
val GetInitialTcState: range * string * TcConfig * TcGlobals * TcImports * TcEnv * OpenDeclaration list -> TcState
@@ -151,6 +185,42 @@ val CheckOneInput:
input: ParsedInput ->
Cancellable<(TcEnv * TopAttribs * CheckedImplFile option * ModuleOrNamespaceType) * TcState>
+val CheckOneInputWithCallback:
+ node: NodeToTypeCheck ->
+ checkForErrors: (unit -> bool) *
+ tcConfig: TcConfig *
+ tcImports: TcImports *
+ tcGlobals: TcGlobals *
+ prefixPathOpt: LongIdent option *
+ tcSink: TcResultsSink *
+ tcState: TcState *
+ input: ParsedInput *
+ _skipImplIfSigExists: bool ->
+ Cancellable>
+
+val AddCheckResultsToTcState:
+ tcGlobals: TcGlobals *
+ amap: Import.ImportMap *
+ hadSig: bool *
+ prefixPathOpt: LongIdent option *
+ tcSink: TcResultsSink *
+ tcImplEnv: TcEnv *
+ qualNameOfFile: QualifiedNameOfFile *
+ implFileSigType: ModuleOrNamespaceType ->
+ tcState: TcState ->
+ ModuleOrNamespaceType * TcState
+
+val AddSignatureResultToTcImplEnv:
+ tcImports: TcImports *
+ tcGlobals: TcGlobals *
+ prefixPathOpt: LongIdent option *
+ tcSink: TcResultsSink *
+ tcState: TcState *
+ input: ParsedInput ->
+ (TcState -> PartialResult * TcState)
+
+val TransformDependencyGraph: graph: Graph * filePairs: FilePairMap -> Graph
+
/// Finish the checking of multiple inputs
val CheckMultipleInputsFinish:
(TcEnv * TopAttribs * 'T option * 'U) list * TcState -> (TcEnv * TopAttribs * 'T list * 'U list) * TcState
diff --git a/src/Compiler/FSharp.Compiler.Service.fsproj b/src/Compiler/FSharp.Compiler.Service.fsproj
index 687bc269233..dd7b6e25c1a 100644
--- a/src/Compiler/FSharp.Compiler.Service.fsproj
+++ b/src/Compiler/FSharp.Compiler.Service.fsproj
@@ -21,6 +21,7 @@
$(OtherFlags) --warnon:3218
$(OtherFlags) --warnon:3390
+
true
$(IntermediateOutputPath)$(TargetFramework)\
$(IntermediateOutputPath)$(TargetFramework)\
@@ -76,7 +77,8 @@
-
+
+
@@ -90,6 +92,7 @@
FSStrings.resx
FSStrings.resources
+
@@ -124,6 +127,8 @@
+
+
@@ -144,6 +149,8 @@
+
+
@@ -156,6 +163,8 @@
+
+
@@ -475,6 +484,10 @@
+
+
+
+
@@ -492,7 +505,12 @@
-
+
+
+
+
+
+
diff --git a/src/Compiler/Facilities/AsyncMemoize.fs b/src/Compiler/Facilities/AsyncMemoize.fs
new file mode 100644
index 00000000000..b780d91ca74
--- /dev/null
+++ b/src/Compiler/Facilities/AsyncMemoize.fs
@@ -0,0 +1,615 @@
+namespace Internal.Utilities.Collections
+
+open System
+open System.Collections.Generic
+open System.Diagnostics
+open System.IO
+open System.Threading
+open System.Threading.Tasks
+
+open FSharp.Compiler
+open FSharp.Compiler.BuildGraph
+open FSharp.Compiler.Diagnostics
+open FSharp.Compiler.DiagnosticsLogger
+open System.Runtime.CompilerServices
+
+[]
+module internal Utils =
+
+ /// Return file name with one directory above it
+ let shortPath path =
+ let dirPath = Path.GetDirectoryName path
+
+ let dir =
+ dirPath.Split Path.DirectorySeparatorChar
+ |> Array.tryLast
+ |> Option.map (sprintf "%s/")
+ |> Option.defaultValue ""
+
+ $"{dir}{Path.GetFileName path}"
+
+ let replayDiagnostics (logger: DiagnosticsLogger) = Seq.iter ((<|) logger.DiagnosticSink)
+
+ let (|TaskCancelled|_|) (ex: exn) =
+ match ex with
+ | :? System.Threading.Tasks.TaskCanceledException as tce -> Some tce
+ //| :? System.AggregateException as ae ->
+ // if ae.InnerExceptions |> Seq.forall (fun e -> e :? System.Threading.Tasks.TaskCanceledException) then
+ // ae.InnerExceptions |> Seq.tryHead |> Option.map (fun e -> e :?> System.Threading.Tasks.TaskCanceledException)
+ // else
+ // None
+ | _ -> None
+
+type internal StateUpdate<'TValue> =
+ | CancelRequest
+ | OriginatorCanceled
+ | JobCompleted of 'TValue * (PhasedDiagnostic * FSharpDiagnosticSeverity) list
+ | JobFailed of exn * (PhasedDiagnostic * FSharpDiagnosticSeverity) list
+
+type internal MemoizeReply<'TValue> =
+ | New of CancellationToken
+ | Existing of Task<'TValue>
+
+type internal MemoizeRequest<'TValue> = GetOrCompute of NodeCode<'TValue> * CancellationToken
+
+[]
+type internal Job<'TValue> =
+ | Running of TaskCompletionSource<'TValue> * CancellationTokenSource * NodeCode<'TValue> * DateTime * ResizeArray
+ | Completed of 'TValue * (PhasedDiagnostic * FSharpDiagnosticSeverity) list
+ | Canceled of DateTime
+ | Failed of DateTime * exn // TODO: probably we don't need to keep this
+
+ member this.DebuggerDisplay =
+ match this with
+ | Running(_, cts, _, ts, _) ->
+ let cancellation =
+ if cts.IsCancellationRequested then
+ " ! Cancellation Requested"
+ else
+ ""
+
+ $"Running since {ts.ToShortTimeString()}{cancellation}"
+ | Completed(value, diags) -> $"Completed {value}" + (if diags.Length > 0 then $" ({diags.Length})" else "")
+ | Canceled _ -> "Canceled"
+ | Failed(_, ex) -> $"Failed {ex}"
+
+type internal JobEvent =
+ | Requested
+ | Started
+ | Restarted
+ | Finished
+ | Canceled
+ | Evicted
+ | Collected
+ | Weakened
+ | Strengthened
+ | Failed
+ | Cleared
+
+type internal ICacheKey<'TKey, 'TVersion> =
+ abstract member GetKey: unit -> 'TKey
+ abstract member GetVersion: unit -> 'TVersion
+ abstract member GetLabel: unit -> string
+
+[]
+type Extensions =
+
+ []
+ static member internal WithExtraVersion(cacheKey: ICacheKey<_, _>, extraVersion) =
+ { new ICacheKey<_, _> with
+ member _.GetLabel() = cacheKey.GetLabel()
+ member _.GetKey() = cacheKey.GetKey()
+ member _.GetVersion() = cacheKey.GetVersion(), extraVersion
+ }
+
+type private KeyData<'TKey, 'TVersion> =
+ {
+ Label: string
+ Key: 'TKey
+ Version: 'TVersion
+ }
+
+type internal AsyncLock() =
+
+ let semaphore = new SemaphoreSlim(1, 1)
+
+ member _.Semaphore = semaphore
+
+ member _.Do(f) =
+ task {
+ do! semaphore.WaitAsync()
+
+ try
+ return! f ()
+ finally
+ semaphore.Release() |> ignore
+ }
+
+ interface IDisposable with
+ member _.Dispose() = semaphore.Dispose()
+
+type internal CachingDiagnosticsLogger(originalLogger: DiagnosticsLogger option) =
+ inherit DiagnosticsLogger($"CachingDiagnosticsLogger")
+
+ let capturedDiagnostics = ResizeArray()
+
+ override _.ErrorCount =
+ originalLogger
+ |> Option.map (fun x -> x.ErrorCount)
+ |> Option.defaultValue capturedDiagnostics.Count
+
+ override _.DiagnosticSink(diagnostic: PhasedDiagnostic, severity: FSharpDiagnosticSeverity) =
+ originalLogger |> Option.iter (fun x -> x.DiagnosticSink(diagnostic, severity))
+ capturedDiagnostics.Add(diagnostic, severity)
+
+ member _.CapturedDiagnostics = capturedDiagnostics |> Seq.toList
+
+[]
+type internal AsyncMemoize<'TKey, 'TVersion, 'TValue when 'TKey: equality and 'TVersion: equality>
+ (?keepStrongly, ?keepWeakly, ?name: string, ?cancelDuplicateRunningJobs: bool) =
+
+ let name = defaultArg name "N/A"
+ let cancelDuplicateRunningJobs = defaultArg cancelDuplicateRunningJobs false
+
+ let event = Event<_>()
+
+ let mutable errors = 0
+ let mutable hits = 0
+ let mutable started = 0
+ let mutable completed = 0
+ let mutable canceled = 0
+ let mutable restarted = 0
+ let mutable failed = 0
+ let mutable evicted = 0
+ let mutable collected = 0
+ let mutable strengthened = 0
+ let mutable cleared = 0
+
+ let mutable cancel_ct_registration_original = 0
+ let mutable cancel_exception_original = 0
+ let mutable cancel_original_processed = 0
+ let mutable cancel_ct_registration_subsequent = 0
+ let mutable cancel_exception_subsequent = 0
+ let mutable cancel_subsequent_processed = 0
+
+ let failures = ResizeArray()
+ let mutable avgDurationMs = 0.0
+
+ let cache =
+ LruCache<'TKey, 'TVersion, Job<'TValue>>(
+ keepStrongly = defaultArg keepStrongly 100,
+ keepWeakly = defaultArg keepWeakly 200,
+ requiredToKeep =
+ (function
+ | Running _ -> true
+ | Job.Canceled at when at > DateTime.Now.AddMinutes -5.0 -> true
+ | Job.Failed(at, _) when at > DateTime.Now.AddMinutes -5.0 -> true
+ | _ -> false),
+ event =
+ (function
+ | CacheEvent.Evicted ->
+ (fun k ->
+ Interlocked.Increment &evicted |> ignore
+ event.Trigger(JobEvent.Evicted, k))
+ | CacheEvent.Collected ->
+ (fun k ->
+ Interlocked.Increment &collected |> ignore
+ event.Trigger(JobEvent.Collected, k))
+ | CacheEvent.Weakened -> (fun k -> event.Trigger(JobEvent.Weakened, k))
+ | CacheEvent.Strengthened ->
+ (fun k ->
+ Interlocked.Increment &strengthened |> ignore
+ event.Trigger(JobEvent.Strengthened, k))
+ | CacheEvent.Cleared ->
+ (fun k ->
+ Interlocked.Increment &cleared |> ignore
+ event.Trigger(JobEvent.Cleared, k)))
+ )
+
+ let requestCounts = Dictionary, int>()
+ let cancellationRegistrations = Dictionary<_, _>()
+
+ let saveRegistration key registration =
+ cancellationRegistrations[key] <-
+ match cancellationRegistrations.TryGetValue key with
+ | true, registrations -> registration :: registrations
+ | _ -> [ registration ]
+
+ let cancelRegistration key =
+ match cancellationRegistrations.TryGetValue key with
+ | true, registrations ->
+ for r: CancellationTokenRegistration in registrations do
+ r.Dispose()
+
+ cancellationRegistrations.Remove key |> ignore
+ | _ -> ()
+
+ let incrRequestCount key =
+ requestCounts[key] <-
+ if requestCounts.ContainsKey key then
+ requestCounts[key] + 1
+ else
+ 1
+
+ let decrRequestCount key =
+ if requestCounts.ContainsKey key then
+ requestCounts[key] <- requestCounts[key] - 1
+
+ let log (eventType, keyData: KeyData<_, _>) =
+ event.Trigger(eventType, (keyData.Label, keyData.Key, keyData.Version))
+
+ let lock = new AsyncLock()
+
+ let processRequest post (key: KeyData<_, _>, msg) diagnosticLogger =
+
+ lock.Do(fun () ->
+ task {
+
+ let cached, otherVersions = cache.GetAll(key.Key, key.Version)
+
+ let result =
+ match msg, cached with
+ | GetOrCompute _, Some(Completed(result, diags)) ->
+ Interlocked.Increment &hits |> ignore
+ diags |> replayDiagnostics diagnosticLogger
+ Existing(Task.FromResult result)
+ | GetOrCompute(_, ct), Some(Running(tcs, _, _, _, loggers)) ->
+ Interlocked.Increment &hits |> ignore
+ incrRequestCount key
+
+ ct.Register(fun _ ->
+ let _name = name
+ Interlocked.Increment &cancel_ct_registration_subsequent |> ignore
+ post (key, CancelRequest))
+ |> saveRegistration key
+
+ loggers.Add diagnosticLogger
+
+ Existing tcs.Task
+
+ | GetOrCompute(computation, ct), None
+ | GetOrCompute(computation, ct), Some(Job.Canceled _)
+ | GetOrCompute(computation, ct), Some(Job.Failed _) ->
+ Interlocked.Increment &started |> ignore
+ incrRequestCount key
+
+ ct.Register(fun _ ->
+ let _name = name
+ Interlocked.Increment &cancel_ct_registration_original |> ignore
+ post (key, OriginatorCanceled))
+ |> saveRegistration key
+
+ let cts = new CancellationTokenSource()
+
+ cache.Set(
+ key.Key,
+ key.Version,
+ key.Label,
+ (Running(TaskCompletionSource(), cts, computation, DateTime.Now, ResizeArray()))
+ )
+
+ otherVersions
+ |> Seq.choose (function
+ | v, Running(_tcs, cts, _, _, _) -> Some(v, cts)
+ | _ -> None)
+ |> Seq.iter (fun (_v, cts) ->
+ use _ = Activity.start $"{name}: Duplicate running job" [| "key", key.Label |]
+ //System.Diagnostics.Trace.TraceWarning($"{name} Duplicate {key.Label}")
+ if cancelDuplicateRunningJobs then
+ //System.Diagnostics.Trace.TraceWarning("Canceling")
+ cts.Cancel())
+
+ New cts.Token
+
+ log (Requested, key)
+ return result
+ })
+
+ let internalError key message =
+ let ex = exn (message)
+ failures.Add(key, ex)
+ Interlocked.Increment &errors |> ignore
+ // raise ex -- Suppose there's no need to raise here - where does it even go?
+
+ let processStateUpdate post (key: KeyData<_, _>, action: StateUpdate<_>) =
+ task {
+ do! Task.Delay 0
+
+ do!
+ lock.Do(fun () ->
+ task {
+
+ let cached = cache.TryGet(key.Key, key.Version)
+
+ match action, cached with
+
+ | OriginatorCanceled, Some(Running(tcs, cts, computation, _, _)) ->
+
+ Interlocked.Increment &cancel_original_processed |> ignore
+
+ decrRequestCount key
+
+ if requestCounts[key] < 1 then
+ cancelRegistration key
+ cts.Cancel()
+ tcs.TrySetCanceled() |> ignore
+ // Remember the job in case it completes after cancellation
+ cache.Set(key.Key, key.Version, key.Label, Job.Canceled DateTime.Now)
+ requestCounts.Remove key |> ignore
+ log (Canceled, key)
+ Interlocked.Increment &canceled |> ignore
+ use _ = Activity.start $"{name}: Canceled job" [| "key", key.Label |]
+ ()
+
+ else
+ // We need to restart the computation
+ Task.Run(fun () ->
+ Async.StartAsTask(
+ async {
+
+ let cachingLogger = new CachingDiagnosticsLogger(None)
+
+ try
+ // TODO: Should unify starting and restarting
+ log (Restarted, key)
+ Interlocked.Increment &restarted |> ignore
+ System.Diagnostics.Trace.TraceInformation $"{name} Restarted {key.Label}"
+ let currentLogger = DiagnosticsThreadStatics.DiagnosticsLogger
+ DiagnosticsThreadStatics.DiagnosticsLogger <- cachingLogger
+
+ try
+ let! result = computation |> Async.AwaitNodeCode
+ post (key, (JobCompleted(result, cachingLogger.CapturedDiagnostics)))
+ return ()
+ finally
+ DiagnosticsThreadStatics.DiagnosticsLogger <- currentLogger
+ with
+ | TaskCancelled _ ->
+ Interlocked.Increment &cancel_exception_subsequent |> ignore
+ post (key, CancelRequest)
+ ()
+ | ex -> post (key, (JobFailed(ex, cachingLogger.CapturedDiagnostics)))
+ }
+ ),
+ cts.Token)
+ |> ignore
+
+ | CancelRequest, Some(Running(tcs, cts, _c, _, _)) ->
+
+ Interlocked.Increment &cancel_subsequent_processed |> ignore
+
+ decrRequestCount key
+
+ if requestCounts[key] < 1 then
+ cancelRegistration key
+ cts.Cancel()
+ tcs.TrySetCanceled() |> ignore
+ // Remember the job in case it completes after cancellation
+ cache.Set(key.Key, key.Version, key.Label, Job.Canceled DateTime.Now)
+ requestCounts.Remove key |> ignore
+ log (Canceled, key)
+ Interlocked.Increment &canceled |> ignore
+ use _ = Activity.start $"{name}: Canceled job" [| "key", key.Label |]
+ ()
+
+ // Probably in some cases cancellation can be fired off even after we just unregistered it
+ | CancelRequest, None
+ | CancelRequest, Some(Completed _)
+ | CancelRequest, Some(Job.Canceled _)
+ | CancelRequest, Some(Job.Failed _)
+ | OriginatorCanceled, None
+ | OriginatorCanceled, Some(Completed _)
+ | OriginatorCanceled, Some(Job.Canceled _)
+ | OriginatorCanceled, Some(Job.Failed _) -> ()
+
+ | JobFailed(ex, diags), Some(Running(tcs, _cts, _c, _ts, loggers)) ->
+ cancelRegistration key
+ cache.Set(key.Key, key.Version, key.Label, Job.Failed(DateTime.Now, ex))
+ requestCounts.Remove key |> ignore
+ log (Failed, key)
+ Interlocked.Increment &failed |> ignore
+ failures.Add(key.Label, ex)
+
+ for logger in loggers do
+ diags |> replayDiagnostics logger
+
+ tcs.TrySetException ex |> ignore
+
+ | JobCompleted(result, diags), Some(Running(tcs, _cts, _c, started, loggers)) ->
+ cancelRegistration key
+ cache.Set(key.Key, key.Version, key.Label, (Completed(result, diags)))
+ requestCounts.Remove key |> ignore
+ log (Finished, key)
+ Interlocked.Increment &completed |> ignore
+ let duration = float (DateTime.Now - started).Milliseconds
+
+ avgDurationMs <-
+ if completed < 2 then
+ duration
+ else
+ avgDurationMs + (duration - avgDurationMs) / float completed
+
+ for logger in loggers do
+ diags |> replayDiagnostics logger
+
+ if tcs.TrySetResult result = false then
+ internalError key.Label "Invalid state: Completed job already completed"
+
+ // Sometimes job can be canceled but it still manages to complete (or fail)
+ | JobFailed _, Some(Job.Canceled _)
+ | JobCompleted _, Some(Job.Canceled _) -> ()
+
+ // Job can't be evicted from cache while it's running because then subsequent requesters would be waiting forever
+ | JobFailed _, None -> internalError key.Label "Invalid state: Running job missing in cache (failed)"
+
+ | JobCompleted _, None -> internalError key.Label "Invalid state: Running job missing in cache (completed)"
+
+ | JobFailed(ex, _diags), Some(Completed(_job, _diags2)) ->
+ internalError key.Label $"Invalid state: Failed Completed job \n%A{ex}"
+
+ | JobCompleted(_result, _diags), Some(Completed(_job, _diags2)) ->
+ internalError key.Label "Invalid state: Double-Completed job"
+
+ | JobFailed(ex, _diags), Some(Job.Failed(_, ex2)) ->
+ internalError key.Label $"Invalid state: Double-Failed job \n%A{ex} \n%A{ex2}"
+
+ | JobCompleted(_result, _diags), Some(Job.Failed(_, ex2)) ->
+ internalError key.Label $"Invalid state: Completed Failed job \n%A{ex2}"
+ })
+ }
+
+ let rec post msg =
+ Task.Run(fun () -> processStateUpdate post msg :> Task) |> ignore
+
+ member this.Get'(key, computation) =
+
+ let wrappedKey =
+ { new ICacheKey<_, _> with
+ member _.GetKey() = key
+ member _.GetVersion() = Unchecked.defaultof<_>
+ member _.GetLabel() = key.ToString()
+ }
+
+ this.Get(wrappedKey, computation)
+
+ member _.Get(key: ICacheKey<_, _>, computation) =
+
+ let key =
+ {
+ Label = key.GetLabel()
+ Key = key.GetKey()
+ Version = key.GetVersion()
+ }
+
+ node {
+ let! ct = NodeCode.CancellationToken
+
+ let callerDiagnosticLogger = DiagnosticsThreadStatics.DiagnosticsLogger
+
+ match!
+ processRequest post (key, GetOrCompute(computation, ct)) callerDiagnosticLogger
+ |> NodeCode.AwaitTask
+ with
+ | New internalCt ->
+
+ let linkedCtSource = CancellationTokenSource.CreateLinkedTokenSource(ct, internalCt)
+ let cachingLogger = new CachingDiagnosticsLogger(Some callerDiagnosticLogger)
+
+ try
+ return!
+ Async.StartAsTask(
+ async {
+ // TODO: Should unify starting and restarting
+ let currentLogger = DiagnosticsThreadStatics.DiagnosticsLogger
+ DiagnosticsThreadStatics.DiagnosticsLogger <- cachingLogger
+
+ log (Started, key)
+
+ try
+ let! result = computation |> Async.AwaitNodeCode
+ post (key, (JobCompleted(result, cachingLogger.CapturedDiagnostics)))
+ return result
+ finally
+ DiagnosticsThreadStatics.DiagnosticsLogger <- currentLogger
+ },
+ cancellationToken = linkedCtSource.Token
+ )
+ |> NodeCode.AwaitTask
+ with
+ | TaskCancelled ex ->
+ // TODO: do we need to do anything else here? Presumably it should be done by the registration on
+ // the cancellation token or before we triggered our own cancellation
+
+ // Let's send this again just in case. It seems sometimes it's not triggered from the registration?
+
+ Interlocked.Increment &cancel_exception_original |> ignore
+
+ post (key, (OriginatorCanceled))
+ return raise ex
+ | ex ->
+ post (key, (JobFailed(ex, cachingLogger.CapturedDiagnostics)))
+ return raise ex
+
+ | Existing job -> return! job |> NodeCode.AwaitTask
+
+ }
+
+ member _.Clear() = cache.Clear()
+
+ member _.Clear predicate = cache.Clear predicate
+
+ member val Event = event.Publish
+
+ member this.OnEvent = this.Event.Add
+
+ member _.Locked = lock.Semaphore.CurrentCount < 1
+
+ member _.Running =
+ cache.GetValues()
+ |> Seq.filter (function
+ | _, _, Running _ -> true
+ | _ -> false)
+ |> Seq.toArray
+
+ member this.DebuggerDisplay =
+ let locked = if this.Locked then " [LOCKED]" else ""
+
+ let valueStats =
+ cache.GetValues()
+ |> Seq.countBy (function
+ | _, _, Running _ -> "Running"
+ | _, _, Completed _ -> "Completed"
+ | _, _, Job.Canceled _ -> "Canceled"
+ | _, _, Job.Failed _ -> "Failed")
+ |> Map
+
+ let running =
+ valueStats.TryFind "Running"
+ |> Option.map (sprintf " Running: %d ")
+ |> Option.defaultValue ""
+
+ let avgDuration = avgDurationMs |> sprintf "| Avg: %.0f ms"
+
+ let hitRatio =
+ if started > 0 then
+ $" (%.0f{float hits / (float (started + hits)) * 100.0} %%)"
+ else
+ ""
+
+ let stats =
+ [|
+ if errors + failed > 0 then
+ " (_!_) "
+ if errors > 0 then $"| ERRORS: {errors} " else ""
+ if failed > 0 then $"| FAILED: {failed} " else ""
+ $"| hits: {hits}{hitRatio} "
+ if started > 0 then $"| started: {started} " else ""
+ if completed > 0 then $"| completed: {completed} " else ""
+ if canceled > 0 then $"| canceled: {canceled} " else ""
+ if restarted > 0 then $"| restarted: {restarted} " else ""
+ if evicted > 0 then $"| evicted: {evicted} " else ""
+ if collected > 0 then $"| collected: {collected} " else ""
+ if cleared > 0 then $"| cleared: {cleared} " else ""
+ if strengthened > 0 then
+ $"| strengthened: {strengthened} "
+ else
+ ""
+ |]
+ |> String.concat ""
+
+ $"{locked}{running}{cache.DebuggerDisplay} {stats}{avgDuration}"
+
+/// A drop-in replacement for AsyncMemoize that disables caching and just runs the computation every time.
+[]
+type internal AsyncMemoizeDisabled<'TKey, 'TVersion, 'TValue when 'TKey: equality and 'TVersion: equality>
+ (?keepStrongly, ?keepWeakly, ?name: string, ?cancelDuplicateRunningJobs: bool) =
+
+ do ignore (keepStrongly, keepWeakly, name, cancelDuplicateRunningJobs)
+
+ let mutable requests = 0
+
+ member _.Get(_key: ICacheKey<_, _>, computation) =
+ Interlocked.Increment &requests |> ignore
+ computation
+
+ member _.DebuggerDisplay = $"(disabled) requests: {requests}"
diff --git a/src/Compiler/Facilities/AsyncMemoize.fsi b/src/Compiler/Facilities/AsyncMemoize.fsi
new file mode 100644
index 00000000000..a34588e7af8
--- /dev/null
+++ b/src/Compiler/Facilities/AsyncMemoize.fsi
@@ -0,0 +1,83 @@
+namespace Internal.Utilities.Collections
+
+open System.Threading.Tasks
+open FSharp.Compiler.BuildGraph
+
+[]
+module internal Utils =
+
+ /// Return file name with one directory above it
+ val shortPath: path: string -> string
+
+ val (|TaskCancelled|_|): ex: exn -> TaskCanceledException option
+
+type internal JobEvent =
+ | Requested
+ | Started
+ | Restarted
+ | Finished
+ | Canceled
+ | Evicted
+ | Collected
+ | Weakened
+ | Strengthened
+ | Failed
+ | Cleared
+
+type internal ICacheKey<'TKey, 'TVersion> =
+
+ abstract GetKey: unit -> 'TKey
+
+ abstract GetLabel: unit -> string
+
+ abstract GetVersion: unit -> 'TVersion
+
+[]
+type Extensions =
+
+ []
+ static member internal WithExtraVersion: cacheKey: ICacheKey<'a, 'b> * extraVersion: 'c -> ICacheKey<'a, ('b * 'c)>
+
+type internal AsyncLock =
+ interface System.IDisposable
+
+ new: unit -> AsyncLock
+
+ member Do: f: (unit -> #Task<'b>) -> Task<'b>
+
+///
+/// A cache/memoization for computations that makes sure that the same computation wil only be computed once even if it's needed
+/// at multiple places/times.
+///
+/// Strongly holds at most one result per key.
+///
+type internal AsyncMemoize<'TKey, 'TVersion, 'TValue when 'TKey: equality and 'TVersion: equality> =
+
+ /// Maximum number of strongly held results to keep in the cache
+ /// Maximum number of weakly held results to keep in the cache
+ /// Name of the cache - used in tracing messages
+ /// If true, when a job is started, all other jobs with the same key will be canceled.
+ new:
+ ?keepStrongly: int * ?keepWeakly: int * ?name: string * ?cancelDuplicateRunningJobs: bool ->
+ AsyncMemoize<'TKey, 'TVersion, 'TValue>
+
+ member Clear: unit -> unit
+
+ member Clear: predicate: ('TKey -> bool) -> unit
+
+ member Get: key: ICacheKey<'TKey, 'TVersion> * computation: NodeCode<'TValue> -> NodeCode<'TValue>
+
+ member Get': key: 'TKey * computation: NodeCode<'TValue> -> NodeCode<'TValue>
+
+ member Event: IEvent
+
+ member OnEvent: ((JobEvent * (string * 'TKey * 'TVersion) -> unit) -> unit)
+
+/// A drop-in replacement for AsyncMemoize that disables caching and just runs the computation every time.
+type internal AsyncMemoizeDisabled<'TKey, 'TVersion, 'TValue when 'TKey: equality and 'TVersion: equality> =
+
+ new:
+ ?keepStrongly: obj * ?keepWeakly: obj * ?name: string * ?cancelDuplicateRunningJobs: bool ->
+ AsyncMemoizeDisabled<'TKey, 'TVersion, 'TValue>
+
+ member Get: _key: ICacheKey<'a, 'b> * computation: 'c -> 'c
diff --git a/src/Compiler/Facilities/BuildGraph.fs b/src/Compiler/Facilities/BuildGraph.fs
index 8927862c23c..1df58c1024b 100644
--- a/src/Compiler/Facilities/BuildGraph.fs
+++ b/src/Compiler/Facilities/BuildGraph.fs
@@ -17,14 +17,12 @@ let wrapThreadStaticInfo computation =
async {
let diagnosticsLogger = DiagnosticsThreadStatics.DiagnosticsLogger
let phase = DiagnosticsThreadStatics.BuildPhase
- let ct = Cancellable.Token
try
return! computation
finally
DiagnosticsThreadStatics.DiagnosticsLogger <- diagnosticsLogger
DiagnosticsThreadStatics.BuildPhase <- phase
- Cancellable.Token <- ct
}
type Async<'T> with
@@ -127,7 +125,6 @@ type NodeCode private () =
static member RunImmediate(computation: NodeCode<'T>, ct: CancellationToken) =
let diagnosticsLogger = DiagnosticsThreadStatics.DiagnosticsLogger
let phase = DiagnosticsThreadStatics.BuildPhase
- let ct2 = Cancellable.Token
try
try
@@ -135,7 +132,6 @@ type NodeCode private () =
async {
DiagnosticsThreadStatics.DiagnosticsLogger <- diagnosticsLogger
DiagnosticsThreadStatics.BuildPhase <- phase
- Cancellable.Token <- ct2
return! computation |> Async.AwaitNodeCode
}
@@ -143,7 +139,6 @@ type NodeCode private () =
finally
DiagnosticsThreadStatics.DiagnosticsLogger <- diagnosticsLogger
DiagnosticsThreadStatics.BuildPhase <- phase
- Cancellable.Token <- ct2
with :? AggregateException as ex when ex.InnerExceptions.Count = 1 ->
raise (ex.InnerExceptions[0])
@@ -153,14 +148,12 @@ type NodeCode private () =
static member StartAsTask_ForTesting(computation: NodeCode<'T>, ?ct: CancellationToken) =
let diagnosticsLogger = DiagnosticsThreadStatics.DiagnosticsLogger
let phase = DiagnosticsThreadStatics.BuildPhase
- let ct2 = Cancellable.Token
try
let work =
async {
DiagnosticsThreadStatics.DiagnosticsLogger <- diagnosticsLogger
DiagnosticsThreadStatics.BuildPhase <- phase
- Cancellable.Token <- ct2
return! computation |> Async.AwaitNodeCode
}
@@ -168,7 +161,6 @@ type NodeCode private () =
finally
DiagnosticsThreadStatics.DiagnosticsLogger <- diagnosticsLogger
DiagnosticsThreadStatics.BuildPhase <- phase
- Cancellable.Token <- ct2
static member CancellationToken = cancellationToken
diff --git a/src/Compiler/Facilities/DiagnosticsLogger.fs b/src/Compiler/Facilities/DiagnosticsLogger.fs
index 24f6ebf2e6d..08a46d1a25d 100644
--- a/src/Compiler/Facilities/DiagnosticsLogger.fs
+++ b/src/Compiler/Facilities/DiagnosticsLogger.fs
@@ -374,7 +374,7 @@ type CapturingDiagnosticsLogger(nm, ?eagerFormat) =
let errors = diagnostics.ToArray()
errors |> Array.iter diagnosticsLogger.DiagnosticSink
-/// Type holds thread-static globals for use by the compile.
+/// Type holds thread-static globals for use by the compiler.
type internal DiagnosticsThreadStatics =
[]
static val mutable private buildPhase: BuildPhase
diff --git a/src/Compiler/Facilities/Hashing.fs b/src/Compiler/Facilities/Hashing.fs
new file mode 100644
index 00000000000..2dfbb38b7ee
--- /dev/null
+++ b/src/Compiler/Facilities/Hashing.fs
@@ -0,0 +1,81 @@
+namespace Internal.Utilities.Hashing
+
+open System
+open System.Threading
+
+/// Tools for hashing things with MD5 into a string that can be used as a cache key.
+module internal Md5StringHasher =
+
+ let private md5 =
+ new ThreadLocal<_>(fun () -> System.Security.Cryptography.MD5.Create())
+
+ let private computeHash (bytes: byte array) = md5.Value.ComputeHash(bytes)
+
+ let hashString (s: string) =
+ System.Text.Encoding.UTF8.GetBytes(s) |> computeHash
+
+ let empty = String.Empty
+
+ let addBytes (bytes: byte array) (s: string) =
+ let sbytes = s |> hashString
+
+ Array.append sbytes bytes
+ |> computeHash
+ |> System.BitConverter.ToString
+ |> (fun x -> x.Replace("-", ""))
+
+ let addString (s: string) (s2: string) =
+ s |> System.Text.Encoding.UTF8.GetBytes |> addBytes <| s2
+
+ let addSeq<'item> (items: 'item seq) (addItem: 'item -> string -> string) (s: string) =
+ items |> Seq.fold (fun s a -> addItem a s) s
+
+ let addStrings strings = addSeq strings addString
+
+ // If we use this make it an extension method?
+ //let addVersions<'a, 'b when 'a :> ICacheKey<'b, string>> (versions: 'a seq) (s: string) =
+ // versions |> Seq.map (fun x -> x.GetVersion()) |> addStrings <| s
+
+ let addBool (b: bool) (s: string) =
+ b |> BitConverter.GetBytes |> addBytes <| s
+
+ let addDateTime (dt: System.DateTime) (s: string) = dt.Ticks.ToString() |> addString <| s
+
+module internal Md5Hasher =
+
+ let private md5 =
+ new ThreadLocal<_>(fun () -> System.Security.Cryptography.MD5.Create())
+
+ let computeHash (bytes: byte array) = md5.Value.ComputeHash(bytes)
+
+ let empty = Array.empty
+
+ let hashString (s: string) =
+ s |> System.Text.Encoding.UTF8.GetBytes |> computeHash
+
+ let addBytes (bytes: byte array) (s: byte array) =
+
+ Array.append s bytes |> computeHash
+
+ let addString (s: string) (s2: byte array) =
+ s |> System.Text.Encoding.UTF8.GetBytes |> addBytes <| s2
+
+ let addSeq<'item> (items: 'item seq) (addItem: 'item -> byte array -> byte array) (s: byte array) =
+ items |> Seq.fold (fun s a -> addItem a s) s
+
+ let addStrings strings = addSeq strings addString
+ let addBytes' bytes = addSeq bytes addBytes
+
+ // If we use this make it an extension method?
+ //let addVersions<'a, 'b when 'a :> ICacheKey<'b, string>> (versions: 'a seq) (s: string) =
+ // versions |> Seq.map (fun x -> x.GetVersion()) |> addStrings <| s
+
+ let addBool (b: bool) (s: byte array) =
+ b |> BitConverter.GetBytes |> addBytes <| s
+
+ let addDateTime (dt: System.DateTime) (s: byte array) =
+ dt.Ticks |> BitConverter.GetBytes |> addBytes <| s
+
+ let addDateTimes (dts: System.DateTime seq) (s: byte array) = s |> addSeq dts addDateTime
+
+ let toString (bytes: byte array) = bytes |> System.BitConverter.ToString
diff --git a/src/Compiler/Facilities/Hashing.fsi b/src/Compiler/Facilities/Hashing.fsi
new file mode 100644
index 00000000000..121afb29eb2
--- /dev/null
+++ b/src/Compiler/Facilities/Hashing.fsi
@@ -0,0 +1,46 @@
+namespace Internal.Utilities.Hashing
+
+/// Tools for hashing things with MD5 into a string that can be used as a cache key.
+module internal Md5StringHasher =
+
+ val hashString: s: string -> byte array
+
+ val empty: string
+
+ val addBytes: bytes: byte array -> s: string -> string
+
+ val addString: s: string -> s2: string -> string
+
+ val addSeq: items: 'item seq -> addItem: ('item -> string -> string) -> s: string -> string
+
+ val addStrings: strings: string seq -> (string -> string)
+
+ val addBool: b: bool -> s: string -> string
+
+ val addDateTime: dt: System.DateTime -> s: string -> string
+
+module internal Md5Hasher =
+
+ val computeHash: bytes: byte array -> byte array
+
+ val empty: 'a array
+
+ val hashString: s: string -> byte array
+
+ val addBytes: bytes: byte array -> s: byte array -> byte array
+
+ val addString: s: string -> s2: byte array -> byte array
+
+ val addSeq: items: 'item seq -> addItem: ('item -> byte array -> byte array) -> s: byte array -> byte array
+
+ val addStrings: strings: string seq -> (byte array -> byte array)
+
+ val addBytes': bytes: byte array seq -> (byte array -> byte array)
+
+ val addBool: b: bool -> s: byte array -> byte array
+
+ val addDateTime: dt: System.DateTime -> s: byte array -> byte array
+
+ val addDateTimes: dts: System.DateTime seq -> s: byte array -> byte array
+
+ val toString: bytes: byte array -> string
diff --git a/src/Compiler/Facilities/prim-lexing.fs b/src/Compiler/Facilities/prim-lexing.fs
index 5951c8338e4..6b927ef4a96 100644
--- a/src/Compiler/Facilities/prim-lexing.fs
+++ b/src/Compiler/Facilities/prim-lexing.fs
@@ -6,8 +6,12 @@ namespace FSharp.Compiler.Text
open System
open System.IO
+open System.Collections.Immutable
open Internal.Utilities.Library
+open Internal.Utilities.Collections
+open Internal.Utilities.Hashing
+
type ISourceText =
abstract Item: index: int -> char with get
@@ -30,6 +34,11 @@ type ISourceText =
abstract GetSubTextFromRange: range: range -> string
+type ISourceTextNew =
+ inherit ISourceText
+
+ abstract GetChecksum: unit -> System.Collections.Immutable.ImmutableArray
+
[]
type StringText(str: string) =
@@ -67,7 +76,7 @@ type StringText(str: string) =
override _.ToString() = str
- interface ISourceText with
+ interface ISourceTextNew with
member _.Item
with get index = str[index]
@@ -145,9 +154,45 @@ type StringText(str: string) =
let lastLine = sourceText.GetLineString(range.EndLine - 1)
sb.Append(lastLine.Substring(0, range.EndColumn)).ToString()
+ member _.GetChecksum() =
+ str |> Md5Hasher.hashString |> ImmutableArray.Create
+
module SourceText =
let ofString str = StringText(str) :> ISourceText
+
+module SourceTextNew =
+
+ let ofString str = StringText(str) :> ISourceTextNew
+
+ let ofISourceText (sourceText: ISourceText) =
+ { new ISourceTextNew with
+ member _.Item
+ with get index = sourceText[index]
+
+ member _.GetLineString(x) = sourceText.GetLineString(x)
+
+ member _.GetLineCount() = sourceText.GetLineCount()
+
+ member _.GetLastCharacterPosition() = sourceText.GetLastCharacterPosition()
+
+ member _.GetSubTextString(x, y) = sourceText.GetSubTextString(x, y)
+
+ member _.SubTextEquals(x, y) = sourceText.SubTextEquals(x, y)
+
+ member _.Length = sourceText.Length
+
+ member _.ContentEquals(x) = sourceText.ContentEquals(x)
+
+ member _.CopyTo(a, b, c, d) = sourceText.CopyTo(a, b, c, d)
+
+ member _.GetSubTextFromRange(x) = sourceText.GetSubTextFromRange(x)
+
+ member _.GetChecksum() =
+ // TODO: something better...
+ sourceText.ToString() |> Md5Hasher.hashString |> ImmutableArray.Create
+ }
+
// NOTE: the code in this file is a drop-in replacement runtime for Lexing.fs from the FsLexYacc repository
namespace Internal.Utilities.Text.Lexing
diff --git a/src/Compiler/Facilities/prim-lexing.fsi b/src/Compiler/Facilities/prim-lexing.fsi
index 6e5f6da4f25..ff13f96c9e1 100644
--- a/src/Compiler/Facilities/prim-lexing.fsi
+++ b/src/Compiler/Facilities/prim-lexing.fsi
@@ -39,12 +39,23 @@ type ISourceText =
/// Throws an exception when the input range is outside the file boundaries.
abstract GetSubTextFromRange: range: range -> string
+/// Just like ISourceText, but with a checksum. Added as a separate type to avoid breaking changes.
+type ISourceTextNew =
+ inherit ISourceText
+
+ abstract GetChecksum: unit -> System.Collections.Immutable.ImmutableArray
+
/// Functions related to ISourceText objects
module SourceText =
/// Creates an ISourceText object from the given string
val ofString: string -> ISourceText
+module SourceTextNew =
+
+ val ofString: string -> ISourceTextNew
+ val ofISourceText: ISourceText -> ISourceTextNew
+
//
// NOTE: the code in this file is a drop-in replacement runtime for Lexing.fsi from the FsLexYacc repository
// and is referenced by generated code for the three FsLex generated lexers in the F# compiler.
diff --git a/src/Compiler/Interactive/fsi.fs b/src/Compiler/Interactive/fsi.fs
index ca0e2335064..e5ff5b6c754 100644
--- a/src/Compiler/Interactive/fsi.fs
+++ b/src/Compiler/Interactive/fsi.fs
@@ -4089,7 +4089,6 @@ type FsiInteractionProcessor
?cancellationToken: CancellationToken
) =
let cancellationToken = defaultArg cancellationToken CancellationToken.None
- use _ = Cancellable.UsingToken(cancellationToken)
if tokenizer.LexBuffer.IsPastEndOfStream then
let stepStatus =
@@ -4218,7 +4217,6 @@ type FsiInteractionProcessor
member _.EvalInteraction(ctok, sourceText, scriptFileName, diagnosticsLogger, ?cancellationToken) =
let cancellationToken = defaultArg cancellationToken CancellationToken.None
- use _ = Cancellable.UsingToken(cancellationToken)
use _ = UseBuildPhase BuildPhase.Interactive
use _ = UseDiagnosticsLogger diagnosticsLogger
use _scope = SetCurrentUICultureForThread fsiOptions.FsiLCID
@@ -4895,7 +4893,6 @@ type FsiEvaluationSession
SpawnInteractiveServer(fsi, fsiOptions, fsiConsoleOutput)
use _ = UseBuildPhase BuildPhase.Interactive
- use _ = Cancellable.UsingToken(CancellationToken.None)
if fsiOptions.Interact then
// page in the type check env
diff --git a/src/Compiler/Optimize/DetupleArgs.fs b/src/Compiler/Optimize/DetupleArgs.fs
index 0021357366c..a1655bafb0b 100644
--- a/src/Compiler/Optimize/DetupleArgs.fs
+++ b/src/Compiler/Optimize/DetupleArgs.fs
@@ -150,14 +150,15 @@ let DetupleRewriteStackGuardDepth = StackGuard.GetDepthOption "DetupleRewrite"
// Merge a tyapp node and and app node.
+[]
let (|TyappAndApp|_|) e =
match e with
| Expr.App(f, fty, tys, args, m) ->
match stripDebugPoints (stripExpr f) with
- | Expr.App(f2, fty2, tys2, [], m2) -> Some(f2, fty2, tys2 @ tys, args, m2)
- | Expr.App _ -> Some(f, fty, tys, args, m) (* has args, so not combine ty args *)
- | f -> Some(f, fty, tys, args, m)
- | _ -> None
+ | Expr.App(f2, fty2, tys2, [], m2) -> ValueSome(f2, fty2, tys2 @ tys, args, m2)
+ | Expr.App _ -> ValueSome(f, fty, tys, args, m) (* has args, so not combine ty args *)
+ | f -> ValueSome(f, fty, tys, args, m)
+ | _ -> ValueNone
[]
module GlobalUsageAnalysis =
diff --git a/src/Compiler/Optimize/LowerComputedCollections.fs b/src/Compiler/Optimize/LowerComputedCollections.fs
index f2f3e4f6245..2de57119ff3 100644
--- a/src/Compiler/Optimize/LowerComputedCollections.fs
+++ b/src/Compiler/Optimize/LowerComputedCollections.fs
@@ -230,27 +230,30 @@ let (|OptionalCoerce|) expr =
// Making 'seq' optional means this kicks in for FSharp.Core, see TcArrayOrListComputedExpression
// which only adds a 'seq' call outside of FSharp.Core
+[]
let (|OptionalSeq|_|) g amap expr =
match expr with
// use 'seq { ... }' as an indicator
| Seq g (e, elemTy) ->
- Some (e, elemTy)
+ ValueSome (e, elemTy)
| _ ->
// search for the relevant element type
match tyOfExpr g expr with
| SeqElemTy g amap expr.Range elemTy ->
- Some (expr, elemTy)
- | _ -> None
+ ValueSome (expr, elemTy)
+ | _ -> ValueNone
+[]
let (|SeqToList|_|) g expr =
match expr with
- | ValApp g g.seq_to_list_vref (_, [seqExpr], m) -> Some (seqExpr, m)
- | _ -> None
+ | ValApp g g.seq_to_list_vref (_, [seqExpr], m) -> ValueSome (seqExpr, m)
+ | _ -> ValueNone
+[]
let (|SeqToArray|_|) g expr =
match expr with
- | ValApp g g.seq_to_array_vref (_, [seqExpr], m) -> Some (seqExpr, m)
- | _ -> None
+ | ValApp g g.seq_to_array_vref (_, [seqExpr], m) -> ValueSome (seqExpr, m)
+ | _ -> ValueNone
let LowerComputedListOrArrayExpr tcVal (g: TcGlobals) amap overallExpr =
// If ListCollector is in FSharp.Core then this optimization kicks in
diff --git a/src/Compiler/Optimize/LowerSequences.fs b/src/Compiler/Optimize/LowerSequences.fs
index 3a7d733ec59..64686d0fe62 100644
--- a/src/Compiler/Optimize/LowerSequences.fs
+++ b/src/Compiler/Optimize/LowerSequences.fs
@@ -74,15 +74,16 @@ let tyConfirmsToSeq g ty =
tyconRefEq g tcref g.tcref_System_Collections_Generic_IEnumerable
| _ -> false
+[]
let (|SeqElemTy|_|) g amap m ty =
match SearchEntireHierarchyOfType (tyConfirmsToSeq g) g amap m ty with
| None ->
// printfn "FAILED - yield! did not yield a sequence! %s" (stringOfRange m)
- None
+ ValueNone
| Some seqTy ->
// printfn "found yield!"
let inpElemTy = List.head (argsOfAppTy g seqTy)
- Some inpElemTy
+ ValueSome inpElemTy
/// Analyze a TAST expression to detect the elaborated form of a sequence expression.
/// Then compile it to a state machine represented as a TAST containing goto, return and label nodes.
diff --git a/src/Compiler/Optimize/LowerSequences.fsi b/src/Compiler/Optimize/LowerSequences.fsi
index aa675cda5c0..61ed7d87766 100644
--- a/src/Compiler/Optimize/LowerSequences.fsi
+++ b/src/Compiler/Optimize/LowerSequences.fsi
@@ -9,7 +9,8 @@ open FSharp.Compiler.TypedTree
open FSharp.Compiler.Text
/// Detect a 'seq' type
-val (|SeqElemTy|_|): TcGlobals -> ImportMap -> range -> TType -> TType option
+[]
+val (|SeqElemTy|_|): TcGlobals -> ImportMap -> range -> TType -> TType voption
val callNonOverloadedILMethod:
g: TcGlobals -> amap: ImportMap -> m: range -> methName: string -> ty: TType -> args: Exprs -> Expr
diff --git a/src/Compiler/Optimize/LowerStateMachines.fs b/src/Compiler/Optimize/LowerStateMachines.fs
index ef578e86064..97d212f8854 100644
--- a/src/Compiler/Optimize/LowerStateMachines.fs
+++ b/src/Compiler/Optimize/LowerStateMachines.fs
@@ -377,6 +377,7 @@ type LowerStateMachine(g: TcGlobals) =
| None -> env2, expr2
// Detect a state machine with a single method override
+ []
let (|ExpandedStateMachineInContext|_|) inputExpr =
// All expanded resumable code state machines e.g. 'task { .. }' begin with a bind of @builder or 'defn'
let env, expr = BindResumableCodeDefinitions env.Empty inputExpr
@@ -405,9 +406,9 @@ type LowerStateMachine(g: TcGlobals) =
(moveNextThisVar, moveNextExprR),
(setStateMachineThisVar, setStateMachineStateVar, setStateMachineBodyR),
(afterCodeThisVar, afterCodeBodyR))
- Some (env, remake2, moveNextBody)
+ ValueSome (env, remake2, moveNextBody)
| _ ->
- None
+ ValueNone
// A utility to add a jump table an expression
let addPcJumpTable m (pcs: int list) (pc2lab: Map) pcExpr expr =
diff --git a/src/Compiler/Optimize/Optimizer.fs b/src/Compiler/Optimize/Optimizer.fs
index e1eaddef8a8..fd3c85dd60b 100644
--- a/src/Compiler/Optimize/Optimizer.fs
+++ b/src/Compiler/Optimize/Optimizer.fs
@@ -230,20 +230,23 @@ type Summary<'Info> =
// Note, this is a different notion of "size" to the one used for inlining heuristics
//-------------------------------------------------------------------------
-let rec SizeOfValueInfos (arr:_[]) =
- if arr.Length <= 0 then 0 else max 0 (SizeOfValueInfo arr[0])
-
-and SizeOfValueInfo x =
- match x with
- | SizeValue (vdepth, _v) -> vdepth // terminate recursion at CACHED size nodes
- | ConstValue (_x, _) -> 1
- | UnknownValue -> 1
- | ValValue (_vr, vinfo) -> SizeOfValueInfo vinfo + 1
- | TupleValue vinfos
- | RecdValue (_, vinfos)
- | UnionCaseValue (_, vinfos) -> 1 + SizeOfValueInfos vinfos
- | CurriedLambdaValue _ -> 1
- | ConstExprValue (_size, _) -> 1
+let SizeOfValueInfo valueInfo =
+ let rec loop acc valueInfo =
+ match valueInfo with
+ | SizeValue (vdepth, _v) -> assert (vdepth >= 0); acc + vdepth // terminate recursion at CACHED size nodes
+ | CurriedLambdaValue _
+ | ConstExprValue _
+ | ConstValue _
+ | UnknownValue -> acc + 1
+ | TupleValue vinfos
+ | RecdValue (_, vinfos)
+ | UnionCaseValue (_, vinfos) when vinfos.Length = 0 -> acc + 1
+ | TupleValue vinfos
+ | RecdValue (_, vinfos)
+ | UnionCaseValue (_, vinfos) -> loop (acc + 1) vinfos[0]
+ | ValValue (_vr, vinfo) -> loop (acc + 1) vinfo
+
+ loop 0 valueInfo
let [] minDepthForASizeNode = 5 // for small vinfos do not record size info, save space
@@ -700,15 +703,17 @@ let rec stripValue = function
| SizeValue(_, details) -> stripValue details (* step through SizeValue "aliases" *)
| vinfo -> vinfo
+[]
let (|StripConstValue|_|) ev =
match stripValue ev with
- | ConstValue(c, _) -> Some c
- | _ -> None
+ | ConstValue(c, _) -> ValueSome c
+ | _ -> ValueNone
+[]
let (|StripLambdaValue|_|) ev =
match stripValue ev with
- | CurriedLambdaValue (id, arity, sz, expr, ty) -> Some (id, arity, sz, expr, ty)
- | _ -> None
+ | CurriedLambdaValue (id, arity, sz, expr, ty) -> ValueSome (id, arity, sz, expr, ty)
+ | _ -> ValueNone
let destTupleValue ev =
match stripValue ev with
@@ -720,10 +725,11 @@ let destRecdValue ev =
| RecdValue (_tcref, info) -> Some info
| _ -> None
+[]
let (|StripUnionCaseValue|_|) ev =
match stripValue ev with
- | UnionCaseValue (c, info) -> Some (c, info)
- | _ -> None
+ | UnionCaseValue (c, info) -> ValueSome (c, info)
+ | _ -> ValueNone
let mkBoolVal (g: TcGlobals) n = ConstValue(Const.Bool n, g.bool_ty)
@@ -1454,11 +1460,11 @@ let AbstractExprInfoByVars (boundVars: Val list, boundTyVars) ivalue =
| UnknownValue -> ivalue
| SizeValue (_vdepth, vinfo) -> MakeSizedValueInfo (abstractExprInfo vinfo)
- and abstractValInfo v =
+ let abstractValInfo v =
{ ValExprInfo=abstractExprInfo v.ValExprInfo
ValMakesNoCriticalTailcalls=v.ValMakesNoCriticalTailcalls }
- and abstractModulInfo ss =
+ let rec abstractModulInfo ss =
{ ModuleOrNamespaceInfos = ss.ModuleOrNamespaceInfos |> NameMap.map (InterruptibleLazy.force >> abstractModulInfo >> notlazy)
ValInfos = ss.ValInfos.Map (fun (vref, e) ->
check vref (abstractValInfo e) ) }
@@ -1589,7 +1595,7 @@ let ValueIsUsedOrHasEffect cenv fvs (b: Binding, binfo) =
// No discarding for things that are used
Zset.contains v (fvs())
-let rec SplitValuesByIsUsedOrHasEffect cenv fvs x =
+let SplitValuesByIsUsedOrHasEffect cenv fvs x =
x |> List.filter (ValueIsUsedOrHasEffect cenv fvs) |> List.unzip
let IlAssemblyCodeInstrHasEffect i =
@@ -1761,26 +1767,29 @@ let TryEliminateLet cenv env bind e2 m =
| None -> mkLetBind m bind e2, 0
/// Detect the application of a value to an arbitrary number of arguments
+[]
let rec (|KnownValApp|_|) expr =
match stripDebugPoints expr with
- | Expr.Val (vref, _, _) -> Some(vref, [], [])
- | Expr.App (KnownValApp(vref, typeArgs1, otherArgs1), _, typeArgs2, otherArgs2, _) -> Some(vref, typeArgs1@typeArgs2, otherArgs1@otherArgs2)
- | _ -> None
+ | Expr.Val (vref, _, _) -> ValueSome(vref, [], [])
+ | Expr.App (KnownValApp(vref, typeArgs1, otherArgs1), _, typeArgs2, otherArgs2, _) -> ValueSome(vref, typeArgs1@typeArgs2, otherArgs1@otherArgs2)
+ | _ -> ValueNone
/// Matches boolean decision tree:
/// check single case with bool const.
+[]
let (|TDBoolSwitch|_|) dtree =
match dtree with
| TDSwitch(expr, [TCase (DecisionTreeTest.Const(Const.Bool testBool), caseTree )], Some defaultTree, range) ->
- Some (expr, testBool, caseTree, defaultTree, range)
+ ValueSome (expr, testBool, caseTree, defaultTree, range)
| _ ->
- None
+ ValueNone
/// Check target that have a constant bool value
+[]
let (|ConstantBoolTarget|_|) target =
match target with
- | TTarget([], Expr.Const (Const.Bool b, _, _), _) -> Some b
- | _ -> None
+ | TTarget([], Expr.Const (Const.Bool b, _, _), _) -> ValueSome b
+ | _ -> ValueNone
/// Is this a tree, where each decision is a two-way switch (to prevent later duplication of trees), and each branch returns or true/false,
/// apart from one branch which defers to another expression
@@ -2013,7 +2022,7 @@ let TryRewriteBranchingTupleBinding g (v: Val) rhs tgtSeqPtOpt body m =
mkLetsBind m binds rhsAndTupleBinding |> Some
| _ -> None
-let rec ExpandStructuralBinding cenv expr =
+let ExpandStructuralBinding cenv expr =
let g = cenv.g
assert cenv.settings.ExpandStructuralValues()
@@ -2050,50 +2059,59 @@ let rec ExpandStructuralBinding cenv expr =
ExpandStructuralBindingRaw cenv e
/// Detect a query { ... }
+[]
let (|QueryRun|_|) g expr =
match expr with
| Expr.App (Expr.Val (vref, _, _), _, _, [_builder; arg], _) when valRefEq g vref g.query_run_value_vref ->
- Some (arg, None)
+ ValueSome (arg, None)
| Expr.App (Expr.Val (vref, _, _), _, [ elemTy ], [_builder; arg], _) when valRefEq g vref g.query_run_enumerable_vref ->
- Some (arg, Some elemTy)
+ ValueSome (arg, Some elemTy)
| _ ->
- None
+ ValueNone
let (|MaybeRefTupled|) e = tryDestRefTupleExpr e
+[]
let (|AnyInstanceMethodApp|_|) e =
match e with
- | Expr.App (Expr.Val (vref, _, _), _, tyargs, [obj; MaybeRefTupled args], _) -> Some (vref, tyargs, obj, args)
- | _ -> None
+ | Expr.App (Expr.Val (vref, _, _), _, tyargs, [obj; MaybeRefTupled args], _) -> ValueSome (vref, tyargs, obj, args)
+ | _ -> ValueNone
+[]
let (|InstanceMethodApp|_|) g (expectedValRef: ValRef) e =
match e with
- | AnyInstanceMethodApp (vref, tyargs, obj, args) when valRefEq g vref expectedValRef -> Some (tyargs, obj, args)
- | _ -> None
+ | AnyInstanceMethodApp (vref, tyargs, obj, args) when valRefEq g vref expectedValRef -> ValueSome (tyargs, obj, args)
+ | _ -> ValueNone
+[]
let (|QuerySourceEnumerable|_|) g = function
- | InstanceMethodApp g g.query_source_vref ([resTy], _builder, [res]) -> Some (resTy, res)
- | _ -> None
+ | InstanceMethodApp g g.query_source_vref ([resTy], _builder, [res]) -> ValueSome (resTy, res)
+ | _ -> ValueNone
+[]
let (|QueryFor|_|) g = function
- | InstanceMethodApp g g.query_for_vref ([srcTy;qTy;resTy;_qInnerTy], _builder, [src;selector]) -> Some (qTy, srcTy, resTy, src, selector)
- | _ -> None
+ | InstanceMethodApp g g.query_for_vref ([srcTy;qTy;resTy;_qInnerTy], _builder, [src;selector]) -> ValueSome (qTy, srcTy, resTy, src, selector)
+ | _ -> ValueNone
+[]
let (|QueryYield|_|) g = function
- | InstanceMethodApp g g.query_yield_vref ([resTy;qTy], _builder, [res]) -> Some (qTy, resTy, res)
- | _ -> None
+ | InstanceMethodApp g g.query_yield_vref ([resTy;qTy], _builder, [res]) -> ValueSome (qTy, resTy, res)
+ | _ -> ValueNone
+[]
let (|QueryYieldFrom|_|) g = function
- | InstanceMethodApp g g.query_yield_from_vref ([resTy;qTy], _builder, [res]) -> Some (qTy, resTy, res)
- | _ -> None
+ | InstanceMethodApp g g.query_yield_from_vref ([resTy;qTy], _builder, [res]) -> ValueSome (qTy, resTy, res)
+ | _ -> ValueNone
+[]
let (|QuerySelect|_|) g = function
- | InstanceMethodApp g g.query_select_vref ([srcTy;qTy;resTy], _builder, [src;selector]) -> Some (qTy, srcTy, resTy, src, selector)
- | _ -> None
+ | InstanceMethodApp g g.query_select_vref ([srcTy;qTy;resTy], _builder, [src;selector]) -> ValueSome (qTy, srcTy, resTy, src, selector)
+ | _ -> ValueNone
+[]
let (|QueryZero|_|) g = function
- | InstanceMethodApp g g.query_zero_vref ([resTy;qTy], _builder, _) -> Some (qTy, resTy)
- | _ -> None
+ | InstanceMethodApp g g.query_zero_vref ([resTy;qTy], _builder, _) -> ValueSome (qTy, resTy)
+ | _ -> ValueNone
/// Look for a possible tuple and transform
let (|AnyRefTupleTrans|) e =
@@ -2102,11 +2120,12 @@ let (|AnyRefTupleTrans|) e =
| _ -> [e], (function [e] -> e | _ -> assert false; failwith "unreachable")
/// Look for any QueryBuilder.* operation and transform
+[]
let (|AnyQueryBuilderOpTrans|_|) g = function
| Expr.App (Expr.Val (vref, _, _) as v, vty, tyargs, [builder; AnyRefTupleTrans( src :: rest, replaceArgs) ], m) when
(match vref.ApparentEnclosingEntity with Parent tcref -> tyconRefEq g tcref g.query_builder_tcref | ParentNone -> false) ->
- Some (src, (fun newSource -> Expr.App (v, vty, tyargs, [builder; replaceArgs(newSource :: rest)], m)))
- | _ -> None
+ ValueSome (src, (fun newSource -> Expr.App (v, vty, tyargs, [builder; replaceArgs(newSource :: rest)], m)))
+ | _ -> ValueNone
/// If this returns "Some" then the source is not IQueryable.
// :=
diff --git a/src/Compiler/Service/BackgroundCompiler.fs b/src/Compiler/Service/BackgroundCompiler.fs
new file mode 100644
index 00000000000..f9f952dde70
--- /dev/null
+++ b/src/Compiler/Service/BackgroundCompiler.fs
@@ -0,0 +1,1680 @@
+namespace FSharp.Compiler.CodeAnalysis
+
+open FSharp.Compiler.Text
+open FSharp.Compiler.BuildGraph
+
+open System
+open System.Diagnostics
+open System.IO
+open System.Reflection
+open System.Reflection.Emit
+open System.Threading
+open Internal.Utilities.Collections
+open Internal.Utilities.Library
+open Internal.Utilities.Library.Extras
+open FSharp.Compiler
+open FSharp.Compiler.AbstractIL
+open FSharp.Compiler.AbstractIL.IL
+open FSharp.Compiler.AbstractIL.ILBinaryReader
+open FSharp.Compiler.AbstractIL.ILDynamicAssemblyWriter
+open FSharp.Compiler.CodeAnalysis
+open FSharp.Compiler.CompilerConfig
+open FSharp.Compiler.CompilerDiagnostics
+open FSharp.Compiler.CompilerImports
+open FSharp.Compiler.CompilerOptions
+open FSharp.Compiler.DependencyManager
+open FSharp.Compiler.Diagnostics
+open FSharp.Compiler.Driver
+open FSharp.Compiler.DiagnosticsLogger
+open FSharp.Compiler.IO
+open FSharp.Compiler.ParseAndCheckInputs
+open FSharp.Compiler.ScriptClosure
+open FSharp.Compiler.Symbols
+open FSharp.Compiler.Syntax
+open FSharp.Compiler.Tokenization
+open FSharp.Compiler.Text
+open FSharp.Compiler.Text.Range
+open FSharp.Compiler.TcGlobals
+open FSharp.Compiler.BuildGraph
+open FSharp.Compiler.CodeAnalysis.ProjectSnapshot
+
+type SourceTextHash = int64
+type CacheStamp = int64
+type FileName = string
+type FilePath = string
+type ProjectPath = string
+type FileVersion = int
+
+type FSharpProjectSnapshot = FSharp.Compiler.CodeAnalysis.ProjectSnapshot.FSharpProjectSnapshot
+
+type internal IBackgroundCompiler =
+
+ /// Type-check the result obtained by parsing. Force the evaluation of the antecedent type checking context if needed.
+ abstract member CheckFileInProject:
+ parseResults: FSharpParseFileResults *
+ fileName: string *
+ fileVersion: int *
+ sourceText: ISourceText *
+ options: FSharpProjectOptions *
+ userOpName: string ->
+ NodeCode
+
+ /// Type-check the result obtained by parsing, but only if the antecedent type checking context is available.
+ abstract member CheckFileInProjectAllowingStaleCachedResults:
+ parseResults: FSharpParseFileResults *
+ fileName: string *
+ fileVersion: int *
+ sourceText: ISourceText *
+ options: FSharpProjectOptions *
+ userOpName: string ->
+ NodeCode
+
+ abstract member ClearCache: options: seq * userOpName: string -> unit
+
+ abstract member ClearCache: projects: ProjectSnapshot.FSharpProjectIdentifier seq * userOpName: string -> unit
+
+ abstract member ClearCaches: unit -> unit
+
+ abstract member DownsizeCaches: unit -> unit
+
+ abstract member FindReferencesInFile:
+ fileName: string *
+ options: FSharpProjectOptions *
+ symbol: FSharp.Compiler.Symbols.FSharpSymbol *
+ canInvalidateProject: bool *
+ userOpName: string ->
+ NodeCode>
+
+ abstract member FindReferencesInFile:
+ fileName: string * projectSnapshot: FSharpProjectSnapshot * symbol: FSharp.Compiler.Symbols.FSharpSymbol * userOpName: string ->
+ NodeCode>
+
+ abstract member GetAssemblyData:
+ options: FSharpProjectOptions * outputFileName: string * userOpName: string ->
+ NodeCode
+
+ abstract member GetAssemblyData:
+ projectSnapshot: FSharpProjectSnapshot * outputFileName: string * userOpName: string ->
+ NodeCode
+
+ /// Fetch the check information from the background compiler (which checks w.r.t. the FileSystem API)
+ abstract member GetBackgroundCheckResultsForFileInProject:
+ fileName: string * options: FSharpProjectOptions * userOpName: string -> NodeCode
+
+ /// Fetch the parse information from the background compiler (which checks w.r.t. the FileSystem API)
+ abstract member GetBackgroundParseResultsForFileInProject:
+ fileName: string * options: FSharpProjectOptions * userOpName: string -> NodeCode
+
+ abstract member GetCachedCheckFileResult:
+ builder: IncrementalBuilder * fileName: string * sourceText: ISourceText * options: FSharpProjectOptions ->
+ NodeCode<(FSharpParseFileResults * FSharpCheckFileResults) option>
+
+ abstract member GetProjectOptionsFromScript:
+ fileName: string *
+ sourceText: ISourceText *
+ previewEnabled: bool option *
+ loadedTimeStamp: System.DateTime option *
+ otherFlags: string array option *
+ useFsiAuxLib: bool option *
+ useSdkRefs: bool option *
+ sdkDirOverride: string option *
+ assumeDotNetFramework: bool option *
+ optionsStamp: int64 option *
+ userOpName: string ->
+ Async
+
+ abstract member GetSemanticClassificationForFile:
+ fileName: string * options: FSharpProjectOptions * userOpName: string ->
+ NodeCode
+
+ abstract member GetSemanticClassificationForFile:
+ fileName: string * snapshot: FSharpProjectSnapshot * userOpName: string ->
+ NodeCode
+
+ abstract member InvalidateConfiguration: options: FSharpProjectOptions * userOpName: string -> unit
+
+ abstract member NotifyFileChanged: fileName: string * options: FSharpProjectOptions * userOpName: string -> NodeCode
+
+ abstract member NotifyProjectCleaned: options: FSharpProjectOptions * userOpName: string -> Async
+
+ /// Parses and checks the source file and returns untyped AST and check results.
+ abstract member ParseAndCheckFileInProject:
+ fileName: string * fileVersion: int * sourceText: ISourceText * options: FSharpProjectOptions * userOpName: string ->
+ NodeCode
+
+ abstract member ParseAndCheckFileInProject:
+ fileName: string * projectSnapshot: FSharpProjectSnapshot * userOpName: string ->
+ NodeCode
+
+ /// Parse and typecheck the whole project.
+ abstract member ParseAndCheckProject: options: FSharpProjectOptions * userOpName: string -> NodeCode
+
+ abstract member ParseAndCheckProject: projectSnapshot: FSharpProjectSnapshot * userOpName: string -> NodeCode
+
+ abstract member ParseFile:
+ fileName: string * sourceText: ISourceText * options: FSharpParsingOptions * cache: bool * flatErrors: bool * userOpName: string ->
+ Async
+
+ abstract member ParseFile:
+ fileName: string * projectSnapshot: FSharpProjectSnapshot * userOpName: string -> Async
+
+ /// Try to get recent approximate type check results for a file.
+ abstract member TryGetRecentCheckResultsForFile:
+ fileName: string * options: FSharpProjectOptions * sourceText: ISourceText option * userOpName: string ->
+ (FSharpParseFileResults * FSharpCheckFileResults * SourceTextHash) option
+
+ abstract member BeforeBackgroundFileCheck: IEvent
+
+ abstract member FileChecked: IEvent
+
+ abstract member FileParsed: IEvent
+
+ abstract member FrameworkImportsCache: FrameworkImportsCache
+
+ abstract member ProjectChecked: IEvent
+
+type internal ParseCacheLockToken() =
+ interface LockToken
+
+type CheckFileCacheKey = FileName * SourceTextHash * FSharpProjectOptions
+type CheckFileCacheValue = FSharpParseFileResults * FSharpCheckFileResults * SourceTextHash * DateTime
+
+[]
+module internal EnvMisc =
+ let braceMatchCacheSize = GetEnvInteger "FCS_BraceMatchCacheSize" 5
+ let parseFileCacheSize = GetEnvInteger "FCS_ParseFileCacheSize" 2
+ let checkFileInProjectCacheSize = GetEnvInteger "FCS_CheckFileInProjectCacheSize" 10
+
+ let projectCacheSizeDefault = GetEnvInteger "FCS_ProjectCacheSizeDefault" 3
+
+ let frameworkTcImportsCacheStrongSize =
+ GetEnvInteger "FCS_frameworkTcImportsCacheStrongSizeDefault" 8
+
+[]
+module internal Helpers =
+
+ /// Determine whether two (fileName,options) keys are identical w.r.t. affect on checking
+ let AreSameForChecking2 ((fileName1: string, options1: FSharpProjectOptions), (fileName2, options2)) =
+ (fileName1 = fileName2)
+ && FSharpProjectOptions.AreSameForChecking(options1, options2)
+
+ /// Determine whether two (fileName,options) keys should be identical w.r.t. resource usage
+ let AreSubsumable2 ((fileName1: string, o1: FSharpProjectOptions), (fileName2: string, o2: FSharpProjectOptions)) =
+ (fileName1 = fileName2) && FSharpProjectOptions.UseSameProject(o1, o2)
+
+ /// Determine whether two (fileName,sourceText,options) keys should be identical w.r.t. parsing
+ let AreSameForParsing ((fileName1: string, source1Hash: int64, options1), (fileName2, source2Hash, options2)) =
+ fileName1 = fileName2 && options1 = options2 && source1Hash = source2Hash
+
+ let AreSimilarForParsing ((fileName1, _, _), (fileName2, _, _)) = fileName1 = fileName2
+
+ /// Determine whether two (fileName,sourceText,options) keys should be identical w.r.t. checking
+ let AreSameForChecking3 ((fileName1: string, source1Hash: int64, options1: FSharpProjectOptions), (fileName2, source2Hash, options2)) =
+ (fileName1 = fileName2)
+ && FSharpProjectOptions.AreSameForChecking(options1, options2)
+ && source1Hash = source2Hash
+
+ /// Determine whether two (fileName,sourceText,options) keys should be identical w.r.t. resource usage
+ let AreSubsumable3 ((fileName1: string, _, o1: FSharpProjectOptions), (fileName2: string, _, o2: FSharpProjectOptions)) =
+ (fileName1 = fileName2) && FSharpProjectOptions.UseSameProject(o1, o2)
+
+ /// If a symbol is an attribute check if given set of names contains its name without the Attribute suffix
+ let rec NamesContainAttribute (symbol: FSharpSymbol) names =
+ match symbol with
+ | :? FSharpMemberOrFunctionOrValue as mofov ->
+ mofov.DeclaringEntity
+ |> Option.map (fun entity -> NamesContainAttribute entity names)
+ |> Option.defaultValue false
+ | :? FSharpEntity as entity when entity.IsAttributeType && symbol.DisplayNameCore.EndsWithOrdinal "Attribute" ->
+ let nameWithoutAttribute = String.dropSuffix symbol.DisplayNameCore "Attribute"
+ names |> Set.contains nameWithoutAttribute
+ | _ -> false
+
+// There is only one instance of this type, held in FSharpChecker
+type internal BackgroundCompiler
+ (
+ legacyReferenceResolver,
+ projectCacheSize,
+ keepAssemblyContents,
+ keepAllBackgroundResolutions,
+ tryGetMetadataSnapshot,
+ suggestNamesForErrors,
+ keepAllBackgroundSymbolUses,
+ enableBackgroundItemKeyStoreAndSemanticClassification,
+ enablePartialTypeChecking,
+ parallelReferenceResolution,
+ captureIdentifiersWhenParsing,
+ getSource: (string -> Async) option,
+ useChangeNotifications,
+ useSyntaxTreeCache
+ ) as self =
+
+ let beforeFileChecked = Event()
+ let fileParsed = Event()
+ let fileChecked = Event()
+ let projectChecked = Event()
+
+ // STATIC ROOT: FSharpLanguageServiceTestable.FSharpChecker.backgroundCompiler.scriptClosureCache
+ /// Information about the derived script closure.
+ let scriptClosureCache =
+ MruCache(
+ projectCacheSize,
+ areSame = FSharpProjectOptions.AreSameForChecking,
+ areSimilar = FSharpProjectOptions.UseSameProject
+ )
+
+ let frameworkTcImportsCache =
+ FrameworkImportsCache(frameworkTcImportsCacheStrongSize)
+
+ // We currently share one global dependency provider for all scripts for the FSharpChecker.
+ // For projects, one is used per project.
+ //
+ // Sharing one for all scripts is necessary for good performance from GetProjectOptionsFromScript,
+ // which requires a dependency provider to process through the project options prior to working out
+ // if the cached incremental builder can be used for the project.
+ let dependencyProviderForScripts = new DependencyProvider()
+
+ let getProjectReferences (options: FSharpProjectOptions) userOpName =
+ [
+ for r in options.ReferencedProjects do
+
+ match r with
+ | FSharpReferencedProject.FSharpReference(nm, opts) ->
+ // Don't use cross-project references for FSharp.Core, since various bits of code
+ // require a concrete FSharp.Core to exist on-disk. The only solutions that have
+ // these cross-project references to FSharp.Core are VisualFSharp.sln and FSharp.sln. The ramification
+ // of this is that you need to build FSharp.Core to get intellisense in those projects.
+
+ if
+ (try
+ Path.GetFileNameWithoutExtension(nm)
+ with _ ->
+ "")
+ <> GetFSharpCoreLibraryName()
+ then
+ { new IProjectReference with
+ member x.EvaluateRawContents() =
+ node {
+ Trace.TraceInformation("FCS: {0}.{1} ({2})", userOpName, "GetAssemblyData", nm)
+ return! self.GetAssemblyData(opts, userOpName + ".CheckReferencedProject(" + nm + ")")
+ }
+
+ member x.TryGetLogicalTimeStamp(cache) =
+ self.TryGetLogicalTimeStampForProject(cache, opts)
+
+ member x.FileName = nm
+ }
+
+ | FSharpReferencedProject.PEReference(getStamp, delayedReader) ->
+ { new IProjectReference with
+ member x.EvaluateRawContents() =
+ node {
+ let! ilReaderOpt = delayedReader.TryGetILModuleReader() |> NodeCode.FromCancellable
+
+ match ilReaderOpt with
+ | Some ilReader ->
+ let ilModuleDef, ilAsmRefs = ilReader.ILModuleDef, ilReader.ILAssemblyRefs
+ let data = RawFSharpAssemblyData(ilModuleDef, ilAsmRefs) :> IRawFSharpAssemblyData
+ return ProjectAssemblyDataResult.Available data
+ | _ ->
+ // Note 'false' - if a PEReference doesn't find an ILModuleReader then we don't
+ // continue to try to use an on-disk DLL
+ return ProjectAssemblyDataResult.Unavailable false
+ }
+
+ member x.TryGetLogicalTimeStamp _ = getStamp () |> Some
+ member x.FileName = delayedReader.OutputFile
+ }
+
+ | FSharpReferencedProject.ILModuleReference(nm, getStamp, getReader) ->
+ { new IProjectReference with
+ member x.EvaluateRawContents() =
+ cancellable {
+ let ilReader = getReader ()
+ let ilModuleDef, ilAsmRefs = ilReader.ILModuleDef, ilReader.ILAssemblyRefs
+ let data = RawFSharpAssemblyData(ilModuleDef, ilAsmRefs) :> IRawFSharpAssemblyData
+ return ProjectAssemblyDataResult.Available data
+ }
+ |> NodeCode.FromCancellable
+
+ member x.TryGetLogicalTimeStamp _ = getStamp () |> Some
+ member x.FileName = nm
+ }
+ ]
+
+ /// CreateOneIncrementalBuilder (for background type checking). Note that fsc.fs also
+ /// creates an incremental builder used by the command line compiler.
+ let CreateOneIncrementalBuilder (options: FSharpProjectOptions, userOpName) =
+ node {
+ use _ =
+ Activity.start "BackgroundCompiler.CreateOneIncrementalBuilder" [| Activity.Tags.project, options.ProjectFileName |]
+
+ Trace.TraceInformation("FCS: {0}.{1} ({2})", userOpName, "CreateOneIncrementalBuilder", options.ProjectFileName)
+ let projectReferences = getProjectReferences options userOpName
+
+ let loadClosure = scriptClosureCache.TryGet(AnyCallerThread, options)
+
+ let dependencyProvider =
+ if options.UseScriptResolutionRules then
+ Some dependencyProviderForScripts
+ else
+ None
+
+ let! builderOpt, diagnostics =
+ IncrementalBuilder.TryCreateIncrementalBuilderForProjectOptions(
+ legacyReferenceResolver,
+ FSharpCheckerResultsSettings.defaultFSharpBinariesDir,
+ frameworkTcImportsCache,
+ loadClosure,
+ Array.toList options.SourceFiles,
+ Array.toList options.OtherOptions,
+ projectReferences,
+ options.ProjectDirectory,
+ options.UseScriptResolutionRules,
+ keepAssemblyContents,
+ keepAllBackgroundResolutions,
+ tryGetMetadataSnapshot,
+ suggestNamesForErrors,
+ keepAllBackgroundSymbolUses,
+ enableBackgroundItemKeyStoreAndSemanticClassification,
+ enablePartialTypeChecking,
+ dependencyProvider,
+ parallelReferenceResolution,
+ captureIdentifiersWhenParsing,
+ getSource,
+ useChangeNotifications,
+ useSyntaxTreeCache
+ )
+
+ match builderOpt with
+ | None -> ()
+ | Some builder ->
+
+#if !NO_TYPEPROVIDERS
+ // Register the behaviour that responds to CCUs being invalidated because of type
+ // provider Invalidate events. This invalidates the configuration in the build.
+ builder.ImportsInvalidatedByTypeProvider.Add(fun () -> self.InvalidateConfiguration(options, userOpName))
+#endif
+
+ // Register the callback called just before a file is typechecked by the background builder (without recording
+ // errors or intellisense information).
+ //
+ // This indicates to the UI that the file type check state is dirty. If the file is open and visible then
+ // the UI will sooner or later request a typecheck of the file, recording errors and intellisense information.
+ builder.BeforeFileChecked.Add(fun file -> beforeFileChecked.Trigger(file, options))
+ builder.FileParsed.Add(fun file -> fileParsed.Trigger(file, options))
+ builder.FileChecked.Add(fun file -> fileChecked.Trigger(file, options))
+ builder.ProjectChecked.Add(fun () -> projectChecked.Trigger options)
+
+ return (builderOpt, diagnostics)
+ }
+
+ let parseCacheLock = Lock()
+
+ // STATIC ROOT: FSharpLanguageServiceTestable.FSharpChecker.parseFileInProjectCache. Most recently used cache for parsing files.
+ let parseFileCache =
+ MruCache(
+ parseFileCacheSize,
+ areSimilar = AreSimilarForParsing,
+ areSame = AreSameForParsing
+ )
+
+ // STATIC ROOT: FSharpLanguageServiceTestable.FSharpChecker.checkFileInProjectCache
+ //
+ /// Cache which holds recently seen type-checks.
+ /// This cache may hold out-of-date entries, in two senses
+ /// - there may be a more recent antecedent state available because the background build has made it available
+ /// - the source for the file may have changed
+
+ // Also keyed on source. This can only be out of date if the antecedent is out of date
+ let checkFileInProjectCache =
+ MruCache>(
+ keepStrongly = checkFileInProjectCacheSize,
+ areSame = AreSameForChecking3,
+ areSimilar = AreSubsumable3
+ )
+
+ // STATIC ROOT: FSharpLanguageServiceTestable.FSharpChecker.backgroundCompiler.incrementalBuildersCache. This root typically holds more
+ // live information than anything else in the F# Language Service, since it holds up to 3 (projectCacheStrongSize) background project builds
+ // strongly.
+ //
+ /// Cache of builds keyed by options.
+ let gate = obj ()
+
+ let incrementalBuildersCache =
+ MruCache>(
+ keepStrongly = projectCacheSize,
+ keepMax = projectCacheSize,
+ areSame = FSharpProjectOptions.AreSameForChecking,
+ areSimilar = FSharpProjectOptions.UseSameProject
+ )
+
+ let tryGetBuilderNode options =
+ incrementalBuildersCache.TryGet(AnyCallerThread, options)
+
+ let tryGetBuilder options : NodeCode option =
+ tryGetBuilderNode options |> Option.map (fun x -> x.GetOrComputeValue())
+
+ let tryGetSimilarBuilder options : NodeCode option =
+ incrementalBuildersCache.TryGetSimilar(AnyCallerThread, options)
+ |> Option.map (fun x -> x.GetOrComputeValue())
+
+ let tryGetAnyBuilder options : NodeCode option =
+ incrementalBuildersCache.TryGetAny(AnyCallerThread, options)
+ |> Option.map (fun x -> x.GetOrComputeValue())
+
+ let createBuilderNode (options, userOpName, ct: CancellationToken) =
+ lock gate (fun () ->
+ if ct.IsCancellationRequested then
+ GraphNode.FromResult(None, [||])
+ else
+ let getBuilderNode = GraphNode(CreateOneIncrementalBuilder(options, userOpName))
+ incrementalBuildersCache.Set(AnyCallerThread, options, getBuilderNode)
+ getBuilderNode)
+
+ let createAndGetBuilder (options, userOpName) =
+ node {
+ let! ct = NodeCode.CancellationToken
+ let getBuilderNode = createBuilderNode (options, userOpName, ct)
+ return! getBuilderNode.GetOrComputeValue()
+ }
+
+ let getOrCreateBuilder (options, userOpName) : NodeCode =
+ match tryGetBuilder options with
+ | Some getBuilder ->
+ node {
+ match! getBuilder with
+ | builderOpt, creationDiags when builderOpt.IsNone || not builderOpt.Value.IsReferencesInvalidated ->
+ return builderOpt, creationDiags
+ | _ ->
+ // The builder could be re-created,
+ // clear the check file caches that are associated with it.
+ // We must do this in order to not return stale results when references
+ // in the project get changed/added/removed.
+ parseCacheLock.AcquireLock(fun ltok ->
+ options.SourceFiles
+ |> Array.iter (fun sourceFile ->
+ let key = (sourceFile, 0L, options)
+ checkFileInProjectCache.RemoveAnySimilar(ltok, key)))
+
+ return! createAndGetBuilder (options, userOpName)
+ }
+ | _ -> createAndGetBuilder (options, userOpName)
+
+ let getSimilarOrCreateBuilder (options, userOpName) =
+ match tryGetSimilarBuilder options with
+ | Some res -> res
+ // The builder does not exist at all. Create it.
+ | None -> getOrCreateBuilder (options, userOpName)
+
+ let getOrCreateBuilderWithInvalidationFlag (options, canInvalidateProject, userOpName) =
+ if canInvalidateProject then
+ getOrCreateBuilder (options, userOpName)
+ else
+ getSimilarOrCreateBuilder (options, userOpName)
+
+ let getAnyBuilder (options, userOpName) =
+ match tryGetAnyBuilder options with
+ | Some getBuilder -> getBuilder
+ | _ -> getOrCreateBuilder (options, userOpName)
+
+ static let mutable actualParseFileCount = 0
+
+ static let mutable actualCheckFileCount = 0
+
+ /// Should be a fast operation. Ensures that we have only one async lazy object per file and its hash.
+ let getCheckFileNode (parseResults, sourceText, fileName, options, _fileVersion, builder, tcPrior, tcInfo, creationDiags) =
+
+ // Here we lock for the creation of the node, not its execution
+ parseCacheLock.AcquireLock(fun ltok ->
+ let key = (fileName, sourceText.GetHashCode() |> int64, options)
+
+ match checkFileInProjectCache.TryGet(ltok, key) with
+ | Some res -> res
+ | _ ->
+ let res =
+ GraphNode(
+ node {
+ let! res =
+ self.CheckOneFileImplAux(
+ parseResults,
+ sourceText,
+ fileName,
+ options,
+ builder,
+ tcPrior,
+ tcInfo,
+ creationDiags
+ )
+
+ Interlocked.Increment(&actualCheckFileCount) |> ignore
+ return res
+ }
+ )
+
+ checkFileInProjectCache.Set(ltok, key, res)
+ res)
+
+ member _.ParseFile
+ (
+ fileName: string,
+ sourceText: ISourceText,
+ options: FSharpParsingOptions,
+ cache: bool,
+ flatErrors: bool,
+ userOpName: string
+ ) =
+ async {
+ use _ =
+ Activity.start
+ "BackgroundCompiler.ParseFile"
+ [|
+ Activity.Tags.fileName, fileName
+ Activity.Tags.userOpName, userOpName
+ Activity.Tags.cache, cache.ToString()
+ |]
+
+ if cache then
+ let hash = sourceText.GetHashCode() |> int64
+
+ match parseCacheLock.AcquireLock(fun ltok -> parseFileCache.TryGet(ltok, (fileName, hash, options))) with
+ | Some res -> return res
+ | None ->
+ Interlocked.Increment(&actualParseFileCount) |> ignore
+ let! ct = Async.CancellationToken
+
+ let parseDiagnostics, parseTree, anyErrors =
+ ParseAndCheckFile.parseFile (
+ sourceText,
+ fileName,
+ options,
+ userOpName,
+ suggestNamesForErrors,
+ flatErrors,
+ captureIdentifiersWhenParsing,
+ ct
+ )
+
+ let res =
+ FSharpParseFileResults(parseDiagnostics, parseTree, anyErrors, options.SourceFiles)
+
+ parseCacheLock.AcquireLock(fun ltok -> parseFileCache.Set(ltok, (fileName, hash, options), res))
+ return res
+ else
+ let! ct = Async.CancellationToken
+
+ let parseDiagnostics, parseTree, anyErrors =
+ ParseAndCheckFile.parseFile (
+ sourceText,
+ fileName,
+ options,
+ userOpName,
+ false,
+ flatErrors,
+ captureIdentifiersWhenParsing,
+ ct
+ )
+
+ return FSharpParseFileResults(parseDiagnostics, parseTree, anyErrors, options.SourceFiles)
+ }
+
+ /// Fetch the parse information from the background compiler (which checks w.r.t. the FileSystem API)
+ member _.GetBackgroundParseResultsForFileInProject(fileName, options, userOpName) =
+ node {
+ use _ =
+ Activity.start
+ "BackgroundCompiler.GetBackgroundParseResultsForFileInProject"
+ [| Activity.Tags.fileName, fileName; Activity.Tags.userOpName, userOpName |]
+
+ let! builderOpt, creationDiags = getOrCreateBuilder (options, userOpName)
+
+ match builderOpt with
+ | None ->
+ let parseTree = EmptyParsedInput(fileName, (false, false))
+ return FSharpParseFileResults(creationDiags, parseTree, true, [||])
+ | Some builder ->
+ let parseTree, _, _, parseDiagnostics = builder.GetParseResultsForFile fileName
+
+ let parseDiagnostics =
+ DiagnosticHelpers.CreateDiagnostics(
+ builder.TcConfig.diagnosticsOptions,
+ false,
+ fileName,
+ parseDiagnostics,
+ suggestNamesForErrors,
+ builder.TcConfig.flatErrors,
+ None
+ )
+
+ let diagnostics = [| yield! creationDiags; yield! parseDiagnostics |]
+
+ let parseResults =
+ FSharpParseFileResults(
+ diagnostics = diagnostics,
+ input = parseTree,
+ parseHadErrors = false,
+ dependencyFiles = builder.AllDependenciesDeprecated
+ )
+
+ return parseResults
+ }
+
+ member _.GetCachedCheckFileResult(builder: IncrementalBuilder, fileName, sourceText: ISourceText, options) =
+ node {
+ use _ =
+ Activity.start "BackgroundCompiler.GetCachedCheckFileResult" [| Activity.Tags.fileName, fileName |]
+
+ let hash = sourceText.GetHashCode() |> int64
+ let key = (fileName, hash, options)
+
+ let cachedResultsOpt =
+ parseCacheLock.AcquireLock(fun ltok -> checkFileInProjectCache.TryGet(ltok, key))
+
+ match cachedResultsOpt with
+ | Some cachedResults ->
+ match! cachedResults.GetOrComputeValue() with
+ | parseResults, checkResults, _, priorTimeStamp when
+ (match builder.GetCheckResultsBeforeFileInProjectEvenIfStale fileName with
+ | None -> false
+ | Some(tcPrior) ->
+ tcPrior.ProjectTimeStamp = priorTimeStamp
+ && builder.AreCheckResultsBeforeFileInProjectReady(fileName))
+ ->
+ return Some(parseResults, checkResults)
+ | _ ->
+ parseCacheLock.AcquireLock(fun ltok -> checkFileInProjectCache.RemoveAnySimilar(ltok, key))
+ return None
+ | _ -> return None
+ }
+
+ member private _.CheckOneFileImplAux
+ (
+ parseResults: FSharpParseFileResults,
+ sourceText: ISourceText,
+ fileName: string,
+ options: FSharpProjectOptions,
+ builder: IncrementalBuilder,
+ tcPrior: PartialCheckResults,
+ tcInfo: TcInfo,
+ creationDiags: FSharpDiagnostic[]
+ ) : NodeCode =
+
+ node {
+ // Get additional script #load closure information if applicable.
+ // For scripts, this will have been recorded by GetProjectOptionsFromScript.
+ let tcConfig = tcPrior.TcConfig
+ let loadClosure = scriptClosureCache.TryGet(AnyCallerThread, options)
+
+ let! checkAnswer =
+ FSharpCheckFileResults.CheckOneFile(
+ parseResults,
+ sourceText,
+ fileName,
+ options.ProjectFileName,
+ tcConfig,
+ tcPrior.TcGlobals,
+ tcPrior.TcImports,
+ tcInfo.tcState,
+ tcInfo.moduleNamesDict,
+ loadClosure,
+ tcInfo.TcDiagnostics,
+ options.IsIncompleteTypeCheckEnvironment,
+ options,
+ Some builder,
+ Array.ofList tcInfo.tcDependencyFiles,
+ creationDiags,
+ parseResults.Diagnostics,
+ keepAssemblyContents,
+ suggestNamesForErrors
+ )
+ |> NodeCode.FromCancellable
+
+ GraphNode.SetPreferredUILang tcConfig.preferredUiLang
+ return (parseResults, checkAnswer, sourceText.GetHashCode() |> int64, tcPrior.ProjectTimeStamp)
+ }
+
+ member private bc.CheckOneFileImpl
+ (
+ parseResults: FSharpParseFileResults,
+ sourceText: ISourceText,
+ fileName: string,
+ options: FSharpProjectOptions,
+ fileVersion: int,
+ builder: IncrementalBuilder,
+ tcPrior: PartialCheckResults,
+ tcInfo: TcInfo,
+ creationDiags: FSharpDiagnostic[]
+ ) =
+
+ node {
+ match! bc.GetCachedCheckFileResult(builder, fileName, sourceText, options) with
+ | Some(_, results) -> return FSharpCheckFileAnswer.Succeeded results
+ | _ ->
+ let lazyCheckFile =
+ getCheckFileNode (parseResults, sourceText, fileName, options, fileVersion, builder, tcPrior, tcInfo, creationDiags)
+
+ let! _, results, _, _ = lazyCheckFile.GetOrComputeValue()
+ return FSharpCheckFileAnswer.Succeeded results
+ }
+
+ /// Type-check the result obtained by parsing, but only if the antecedent type checking context is available.
+ member bc.CheckFileInProjectAllowingStaleCachedResults
+ (
+ parseResults: FSharpParseFileResults,
+ fileName,
+ fileVersion,
+ sourceText: ISourceText,
+ options,
+ userOpName
+ ) =
+ node {
+ use _ =
+ Activity.start
+ "BackgroundCompiler.CheckFileInProjectAllowingStaleCachedResults"
+ [|
+ Activity.Tags.project, options.ProjectFileName
+ Activity.Tags.fileName, fileName
+ Activity.Tags.userOpName, userOpName
+ |]
+
+ let! cachedResults =
+ node {
+ let! builderOpt, creationDiags = getAnyBuilder (options, userOpName)
+
+ match builderOpt with
+ | Some builder ->
+ match! bc.GetCachedCheckFileResult(builder, fileName, sourceText, options) with
+ | Some(_, checkResults) -> return Some(builder, creationDiags, Some(FSharpCheckFileAnswer.Succeeded checkResults))
+ | _ -> return Some(builder, creationDiags, None)
+ | _ -> return None // the builder wasn't ready
+ }
+
+ match cachedResults with
+ | None -> return None
+ | Some(_, _, Some x) -> return Some x
+ | Some(builder, creationDiags, None) ->
+ Trace.TraceInformation("FCS: {0}.{1} ({2})", userOpName, "CheckFileInProjectAllowingStaleCachedResults.CacheMiss", fileName)
+
+ match builder.GetCheckResultsBeforeFileInProjectEvenIfStale fileName with
+ | Some tcPrior ->
+ match tcPrior.TryPeekTcInfo() with
+ | Some tcInfo ->
+ let! checkResults =
+ bc.CheckOneFileImpl(
+ parseResults,
+ sourceText,
+ fileName,
+ options,
+ fileVersion,
+ builder,
+ tcPrior,
+ tcInfo,
+ creationDiags
+ )
+
+ return Some checkResults
+ | None -> return None
+ | None -> return None // the incremental builder was not up to date
+ }
+
+ /// Type-check the result obtained by parsing. Force the evaluation of the antecedent type checking context if needed.
+ member bc.CheckFileInProject
+ (
+ parseResults: FSharpParseFileResults,
+ fileName,
+ fileVersion,
+ sourceText: ISourceText,
+ options,
+ userOpName
+ ) =
+ node {
+ use _ =
+ Activity.start
+ "BackgroundCompiler.CheckFileInProject"
+ [|
+ Activity.Tags.project, options.ProjectFileName
+ Activity.Tags.fileName, fileName
+ Activity.Tags.userOpName, userOpName
+ |]
+
+ let! builderOpt, creationDiags = getOrCreateBuilder (options, userOpName)
+
+ match builderOpt with
+ | None ->
+ return FSharpCheckFileAnswer.Succeeded(FSharpCheckFileResults.MakeEmpty(fileName, creationDiags, keepAssemblyContents))
+ | Some builder ->
+ // Check the cache. We can only use cached results when there is no work to do to bring the background builder up-to-date
+ let! cachedResults = bc.GetCachedCheckFileResult(builder, fileName, sourceText, options)
+
+ match cachedResults with
+ | Some(_, checkResults) -> return FSharpCheckFileAnswer.Succeeded checkResults
+ | _ ->
+ let! tcPrior = builder.GetCheckResultsBeforeFileInProject fileName
+ let! tcInfo = tcPrior.GetOrComputeTcInfo()
+
+ return!
+ bc.CheckOneFileImpl(
+ parseResults,
+ sourceText,
+ fileName,
+ options,
+ fileVersion,
+ builder,
+ tcPrior,
+ tcInfo,
+ creationDiags
+ )
+ }
+
+ /// Parses and checks the source file and returns untyped AST and check results.
+ member bc.ParseAndCheckFileInProject
+ (
+ fileName: string,
+ fileVersion,
+ sourceText: ISourceText,
+ options: FSharpProjectOptions,
+ userOpName
+ ) =
+ node {
+ use _ =
+ Activity.start
+ "BackgroundCompiler.ParseAndCheckFileInProject"
+ [|
+ Activity.Tags.project, options.ProjectFileName
+ Activity.Tags.fileName, fileName
+ Activity.Tags.userOpName, userOpName
+ |]
+
+ let! builderOpt, creationDiags = getOrCreateBuilder (options, userOpName)
+
+ match builderOpt with
+ | None ->
+ let parseTree = EmptyParsedInput(fileName, (false, false))
+ let parseResults = FSharpParseFileResults(creationDiags, parseTree, true, [||])
+ return (parseResults, FSharpCheckFileAnswer.Aborted)
+
+ | Some builder ->
+ let! cachedResults = bc.GetCachedCheckFileResult(builder, fileName, sourceText, options)
+
+ match cachedResults with
+ | Some(parseResults, checkResults) -> return (parseResults, FSharpCheckFileAnswer.Succeeded checkResults)
+ | _ ->
+ let! tcPrior = builder.GetCheckResultsBeforeFileInProject fileName
+ let! tcInfo = tcPrior.GetOrComputeTcInfo()
+ // Do the parsing.
+ let parsingOptions =
+ FSharpParsingOptions.FromTcConfig(
+ builder.TcConfig,
+ Array.ofList builder.SourceFiles,
+ options.UseScriptResolutionRules
+ )
+
+ GraphNode.SetPreferredUILang tcPrior.TcConfig.preferredUiLang
+ let! ct = NodeCode.CancellationToken
+
+ let parseDiagnostics, parseTree, anyErrors =
+ ParseAndCheckFile.parseFile (
+ sourceText,
+ fileName,
+ parsingOptions,
+ userOpName,
+ suggestNamesForErrors,
+ builder.TcConfig.flatErrors,
+ captureIdentifiersWhenParsing,
+ ct
+ )
+
+ let parseResults =
+ FSharpParseFileResults(parseDiagnostics, parseTree, anyErrors, builder.AllDependenciesDeprecated)
+
+ let! checkResults =
+ bc.CheckOneFileImpl(
+ parseResults,
+ sourceText,
+ fileName,
+ options,
+ fileVersion,
+ builder,
+ tcPrior,
+ tcInfo,
+ creationDiags
+ )
+
+ return (parseResults, checkResults)
+ }
+
+ member _.NotifyFileChanged(fileName, options, userOpName) =
+ node {
+ use _ =
+ Activity.start
+ "BackgroundCompiler.NotifyFileChanged"
+ [|
+ Activity.Tags.project, options.ProjectFileName
+ Activity.Tags.fileName, fileName
+ Activity.Tags.userOpName, userOpName
+ |]
+
+ let! builderOpt, _ = getOrCreateBuilder (options, userOpName)
+
+ match builderOpt with
+ | None -> return ()
+ | Some builder -> do! builder.NotifyFileChanged(fileName, DateTime.UtcNow)
+ }
+
+ /// Fetch the check information from the background compiler (which checks w.r.t. the FileSystem API)
+ member _.GetBackgroundCheckResultsForFileInProject(fileName, options, userOpName) =
+ node {
+ use _ =
+ Activity.start
+ "BackgroundCompiler.ParseAndCheckFileInProject"
+ [|
+ Activity.Tags.project, options.ProjectFileName
+ Activity.Tags.fileName, fileName
+ Activity.Tags.userOpName, userOpName
+ |]
+
+ let! builderOpt, creationDiags = getOrCreateBuilder (options, userOpName)
+
+ match builderOpt with
+ | None ->
+ let parseTree = EmptyParsedInput(fileName, (false, false))
+ let parseResults = FSharpParseFileResults(creationDiags, parseTree, true, [||])
+ let typedResults = FSharpCheckFileResults.MakeEmpty(fileName, creationDiags, true)
+ return (parseResults, typedResults)
+ | Some builder ->
+ let parseTree, _, _, parseDiagnostics = builder.GetParseResultsForFile fileName
+ let! tcProj = builder.GetFullCheckResultsAfterFileInProject fileName
+
+ let! tcInfo, tcInfoExtras = tcProj.GetOrComputeTcInfoWithExtras()
+
+ let tcResolutions = tcInfoExtras.tcResolutions
+ let tcSymbolUses = tcInfoExtras.tcSymbolUses
+ let tcOpenDeclarations = tcInfoExtras.tcOpenDeclarations
+ let latestCcuSigForFile = tcInfo.latestCcuSigForFile
+ let tcState = tcInfo.tcState
+ let tcEnvAtEnd = tcInfo.tcEnvAtEndOfFile
+ let latestImplementationFile = tcInfoExtras.latestImplFile
+ let tcDependencyFiles = tcInfo.tcDependencyFiles
+ let tcDiagnostics = tcInfo.TcDiagnostics
+ let diagnosticsOptions = builder.TcConfig.diagnosticsOptions
+
+ let symbolEnv =
+ SymbolEnv(tcProj.TcGlobals, tcInfo.tcState.Ccu, Some tcInfo.tcState.CcuSig, tcProj.TcImports)
+ |> Some
+
+ let parseDiagnostics =
+ DiagnosticHelpers.CreateDiagnostics(
+ diagnosticsOptions,
+ false,
+ fileName,
+ parseDiagnostics,
+ suggestNamesForErrors,
+ builder.TcConfig.flatErrors,
+ None
+ )
+
+ let parseDiagnostics = [| yield! creationDiags; yield! parseDiagnostics |]
+
+ let tcDiagnostics =
+ DiagnosticHelpers.CreateDiagnostics(
+ diagnosticsOptions,
+ false,
+ fileName,
+ tcDiagnostics,
+ suggestNamesForErrors,
+ builder.TcConfig.flatErrors,
+ symbolEnv
+ )
+
+ let tcDiagnostics = [| yield! creationDiags; yield! tcDiagnostics |]
+
+ let parseResults =
+ FSharpParseFileResults(
+ diagnostics = parseDiagnostics,
+ input = parseTree,
+ parseHadErrors = false,
+ dependencyFiles = builder.AllDependenciesDeprecated
+ )
+
+ let loadClosure = scriptClosureCache.TryGet(AnyCallerThread, options)
+
+ let typedResults =
+ FSharpCheckFileResults.Make(
+ fileName,
+ options.ProjectFileName,
+ tcProj.TcConfig,
+ tcProj.TcGlobals,
+ options.IsIncompleteTypeCheckEnvironment,
+ Some builder,
+ options,
+ Array.ofList tcDependencyFiles,
+ creationDiags,
+ parseResults.Diagnostics,
+ tcDiagnostics,
+ keepAssemblyContents,
+ Option.get latestCcuSigForFile,
+ tcState.Ccu,
+ tcProj.TcImports,
+ tcEnvAtEnd.AccessRights,
+ tcResolutions,
+ tcSymbolUses,
+ tcEnvAtEnd.NameEnv,
+ loadClosure,
+ latestImplementationFile,
+ tcOpenDeclarations
+ )
+
+ return (parseResults, typedResults)
+ }
+
+ member _.FindReferencesInFile
+ (
+ fileName: string,
+ options: FSharpProjectOptions,
+ symbol: FSharpSymbol,
+ canInvalidateProject: bool,
+ userOpName: string
+ ) =
+ node {
+ use _ =
+ Activity.start
+ "BackgroundCompiler.FindReferencesInFile"
+ [|
+ Activity.Tags.project, options.ProjectFileName
+ Activity.Tags.fileName, fileName
+ Activity.Tags.userOpName, userOpName
+ "symbol", symbol.FullName
+ |]
+
+ let! builderOpt, _ = getOrCreateBuilderWithInvalidationFlag (options, canInvalidateProject, userOpName)
+
+ match builderOpt with
+ | None -> return Seq.empty
+ | Some builder ->
+ if builder.ContainsFile fileName then
+ let! checkResults = builder.GetFullCheckResultsAfterFileInProject fileName
+ let! keyStoreOpt = checkResults.GetOrComputeItemKeyStoreIfEnabled()
+
+ match keyStoreOpt with
+ | None -> return Seq.empty
+ | Some reader -> return reader.FindAll symbol.Item
+ else
+ return Seq.empty
+ }
+
+ member _.GetSemanticClassificationForFile(fileName: string, options: FSharpProjectOptions, userOpName: string) =
+ node {
+ use _ =
+ Activity.start
+ "BackgroundCompiler.GetSemanticClassificationForFile"
+ [|
+ Activity.Tags.project, options.ProjectFileName
+ Activity.Tags.fileName, fileName
+ Activity.Tags.userOpName, userOpName
+ |]
+
+ let! builderOpt, _ = getOrCreateBuilder (options, userOpName)
+
+ match builderOpt with
+ | None -> return None
+ | Some builder ->
+ let! checkResults = builder.GetFullCheckResultsAfterFileInProject fileName
+ let! scopt = checkResults.GetOrComputeSemanticClassificationIfEnabled()
+
+ match scopt with
+ | None -> return None
+ | Some sc -> return Some(sc.GetView())
+ }
+
+ /// Try to get recent approximate type check results for a file.
+ member _.TryGetRecentCheckResultsForFile
+ (
+ fileName: string,
+ options: FSharpProjectOptions,
+ sourceText: ISourceText option,
+ _userOpName: string
+ ) =
+ use _ =
+ Activity.start
+ "BackgroundCompiler.GetSemanticClassificationForFile"
+ [|
+ Activity.Tags.project, options.ProjectFileName
+ Activity.Tags.fileName, fileName
+ Activity.Tags.userOpName, _userOpName
+ |]
+
+ match sourceText with
+ | Some sourceText ->
+ let hash = sourceText.GetHashCode() |> int64
+
+ let resOpt =
+ parseCacheLock.AcquireLock(fun ltok -> checkFileInProjectCache.TryGet(ltok, (fileName, hash, options)))
+
+ match resOpt with
+ | Some res ->
+ match res.TryPeekValue() with
+ | ValueSome(a, b, c, _) -> Some(a, b, c)
+ | ValueNone -> None
+ | None -> None
+ | None -> None
+
+ /// Parse and typecheck the whole project (the implementation, called recursively as project graph is evaluated)
+ member private _.ParseAndCheckProjectImpl(options, userOpName) =
+ node {
+
+ let! builderOpt, creationDiags = getOrCreateBuilder (options, userOpName)
+
+ match builderOpt with
+ | None ->
+ let emptyResults =
+ FSharpCheckProjectResults(options.ProjectFileName, None, keepAssemblyContents, creationDiags, None)
+
+ return emptyResults
+ | Some builder ->
+ let! tcProj, ilAssemRef, tcAssemblyDataOpt, tcAssemblyExprOpt = builder.GetFullCheckResultsAndImplementationsForProject()
+ let diagnosticsOptions = tcProj.TcConfig.diagnosticsOptions
+ let fileName = DummyFileNameForRangesWithoutASpecificLocation
+
+ // Although we do not use 'tcInfoExtras', computing it will make sure we get an extra info.
+ let! tcInfo, _tcInfoExtras = tcProj.GetOrComputeTcInfoWithExtras()
+
+ let topAttribs = tcInfo.topAttribs
+ let tcState = tcInfo.tcState
+ let tcEnvAtEnd = tcInfo.tcEnvAtEndOfFile
+ let tcDiagnostics = tcInfo.TcDiagnostics
+ let tcDependencyFiles = tcInfo.tcDependencyFiles
+
+ let symbolEnv =
+ SymbolEnv(tcProj.TcGlobals, tcInfo.tcState.Ccu, Some tcInfo.tcState.CcuSig, tcProj.TcImports)
+ |> Some
+
+ let tcDiagnostics =
+ DiagnosticHelpers.CreateDiagnostics(
+ diagnosticsOptions,
+ true,
+ fileName,
+ tcDiagnostics,
+ suggestNamesForErrors,
+ builder.TcConfig.flatErrors,
+ symbolEnv
+ )
+
+ let diagnostics = [| yield! creationDiags; yield! tcDiagnostics |]
+
+ let getAssemblyData () =
+ match tcAssemblyDataOpt with
+ | ProjectAssemblyDataResult.Available data -> Some data
+ | _ -> None
+
+ let details =
+ (tcProj.TcGlobals,
+ tcProj.TcImports,
+ tcState.Ccu,
+ tcState.CcuSig,
+ Choice1Of2 builder,
+ topAttribs,
+ getAssemblyData,
+ ilAssemRef,
+ tcEnvAtEnd.AccessRights,
+ tcAssemblyExprOpt,
+ Array.ofList tcDependencyFiles,
+ options)
+
+ let results =
+ FSharpCheckProjectResults(
+ options.ProjectFileName,
+ Some tcProj.TcConfig,
+ keepAssemblyContents,
+ diagnostics,
+ Some details
+ )
+
+ return results
+ }
+
+ member _.GetAssemblyData(options, userOpName) =
+ node {
+ use _ =
+ Activity.start
+ "BackgroundCompiler.GetAssemblyData"
+ [|
+ Activity.Tags.project, options.ProjectFileName
+ Activity.Tags.userOpName, userOpName
+ |]
+
+ let! builderOpt, _ = getOrCreateBuilder (options, userOpName)
+
+ match builderOpt with
+ | None -> return ProjectAssemblyDataResult.Unavailable true
+ | Some builder ->
+ let! _, _, tcAssemblyDataOpt, _ = builder.GetCheckResultsAndImplementationsForProject()
+ return tcAssemblyDataOpt
+ }
+
+ /// Get the timestamp that would be on the output if fully built immediately
+ member private _.TryGetLogicalTimeStampForProject(cache, options) =
+ match tryGetBuilderNode options with
+ | Some lazyWork ->
+ match lazyWork.TryPeekValue() with
+ | ValueSome(Some builder, _) -> Some(builder.GetLogicalTimeStampForProject(cache))
+ | _ -> None
+ | _ -> None
+
+ /// Parse and typecheck the whole project.
+ member bc.ParseAndCheckProject(options, userOpName) =
+ use _ =
+ Activity.start
+ "BackgroundCompiler.ParseAndCheckProject"
+ [|
+ Activity.Tags.project, options.ProjectFileName
+ Activity.Tags.userOpName, userOpName
+ |]
+
+ bc.ParseAndCheckProjectImpl(options, userOpName)
+
+ member _.GetProjectOptionsFromScript
+ (
+ fileName,
+ sourceText,
+ previewEnabled,
+ loadedTimeStamp,
+ otherFlags,
+ useFsiAuxLib: bool option,
+ useSdkRefs: bool option,
+ sdkDirOverride: string option,
+ assumeDotNetFramework: bool option,
+ optionsStamp: int64 option,
+ _userOpName
+ ) =
+ use _ =
+ Activity.start
+ "BackgroundCompiler.GetProjectOptionsFromScript"
+ [| Activity.Tags.fileName, fileName; Activity.Tags.userOpName, _userOpName |]
+
+ cancellable {
+ // Do we add a reference to FSharp.Compiler.Interactive.Settings by default?
+ let useFsiAuxLib = defaultArg useFsiAuxLib true
+ let useSdkRefs = defaultArg useSdkRefs true
+ let reduceMemoryUsage = ReduceMemoryFlag.Yes
+ let previewEnabled = defaultArg previewEnabled false
+
+ // Do we assume .NET Framework references for scripts?
+ let assumeDotNetFramework = defaultArg assumeDotNetFramework true
+
+ let! ct = Cancellable.token ()
+ use _ = Cancellable.UsingToken(ct)
+
+ let extraFlags =
+ if previewEnabled then
+ [| "--langversion:preview" |]
+ else
+ [||]
+
+ let otherFlags = defaultArg otherFlags extraFlags
+
+ use diagnostics = new DiagnosticsScope(otherFlags |> Array.contains "--flaterrors")
+
+ let useSimpleResolution =
+ otherFlags |> Array.exists (fun x -> x = "--simpleresolution")
+
+ let loadedTimeStamp = defaultArg loadedTimeStamp DateTime.MaxValue // Not 'now', we don't want to force reloading
+
+ let applyCompilerOptions tcConfigB =
+ let fsiCompilerOptions = GetCoreFsiCompilerOptions tcConfigB
+ ParseCompilerOptions(ignore, fsiCompilerOptions, Array.toList otherFlags)
+
+ let loadClosure =
+ LoadClosure.ComputeClosureOfScriptText(
+ legacyReferenceResolver,
+ FSharpCheckerResultsSettings.defaultFSharpBinariesDir,
+ fileName,
+ sourceText,
+ CodeContext.Editing,
+ useSimpleResolution,
+ useFsiAuxLib,
+ useSdkRefs,
+ sdkDirOverride,
+ Lexhelp.LexResourceManager(),
+ applyCompilerOptions,
+ assumeDotNetFramework,
+ tryGetMetadataSnapshot,
+ reduceMemoryUsage,
+ dependencyProviderForScripts
+ )
+
+ let otherFlags =
+ [|
+ yield "--noframework"
+ yield "--warn:3"
+ yield! otherFlags
+ for r in loadClosure.References do
+ yield "-r:" + fst r
+ for code, _ in loadClosure.NoWarns do
+ yield "--nowarn:" + code
+ |]
+
+ let options =
+ {
+ ProjectFileName = fileName + ".fsproj" // Make a name that is unique in this directory.
+ ProjectId = None
+ SourceFiles = loadClosure.SourceFiles |> List.map fst |> List.toArray
+ OtherOptions = otherFlags
+ ReferencedProjects = [||]
+ IsIncompleteTypeCheckEnvironment = false
+ UseScriptResolutionRules = true
+ LoadTime = loadedTimeStamp
+ UnresolvedReferences = Some(FSharpUnresolvedReferencesSet(loadClosure.UnresolvedReferences))
+ OriginalLoadReferences = loadClosure.OriginalLoadReferences
+ Stamp = optionsStamp
+ }
+
+ scriptClosureCache.Set(AnyCallerThread, options, loadClosure) // Save the full load closure for later correlation.
+
+ let diags =
+ loadClosure.LoadClosureRootFileDiagnostics
+ |> List.map (fun (exn, isError) ->
+ FSharpDiagnostic.CreateFromException(
+ exn,
+ isError,
+ range.Zero,
+ false,
+ options.OtherOptions |> Array.contains "--flaterrors",
+ None
+ ))
+
+ return options, (diags @ diagnostics.Diagnostics)
+ }
+ |> Cancellable.toAsync
+
+ member bc.InvalidateConfiguration(options: FSharpProjectOptions, userOpName) =
+ use _ =
+ Activity.start
+ "BackgroundCompiler.InvalidateConfiguration"
+ [|
+ Activity.Tags.project, options.ProjectFileName
+ Activity.Tags.userOpName, userOpName
+ |]
+
+ if incrementalBuildersCache.ContainsSimilarKey(AnyCallerThread, options) then
+ parseCacheLock.AcquireLock(fun ltok ->
+ for sourceFile in options.SourceFiles do
+ checkFileInProjectCache.RemoveAnySimilar(ltok, (sourceFile, 0L, options)))
+
+ let _ = createBuilderNode (options, userOpName, CancellationToken.None)
+ ()
+
+ member bc.ClearCache(options: seq, _userOpName) =
+ use _ =
+ Activity.start "BackgroundCompiler.ClearCache" [| Activity.Tags.userOpName, _userOpName |]
+
+ lock gate (fun () ->
+ options
+ |> Seq.iter (fun options ->
+ incrementalBuildersCache.RemoveAnySimilar(AnyCallerThread, options)
+
+ parseCacheLock.AcquireLock(fun ltok ->
+ for sourceFile in options.SourceFiles do
+ checkFileInProjectCache.RemoveAnySimilar(ltok, (sourceFile, 0L, options)))))
+
+ member _.NotifyProjectCleaned(options: FSharpProjectOptions, userOpName) =
+ use _ =
+ Activity.start
+ "BackgroundCompiler.NotifyProjectCleaned"
+ [|
+ Activity.Tags.project, options.ProjectFileName
+ Activity.Tags.userOpName, userOpName
+ |]
+
+ async {
+
+ let! ct = Async.CancellationToken
+ // If there was a similar entry (as there normally will have been) then re-establish an empty builder . This
+ // is a somewhat arbitrary choice - it will have the effect of releasing memory associated with the previous
+ // builder, but costs some time.
+ if incrementalBuildersCache.ContainsSimilarKey(AnyCallerThread, options) then
+ let _ = createBuilderNode (options, userOpName, ct)
+ ()
+ }
+
+ member _.BeforeBackgroundFileCheck = beforeFileChecked.Publish
+
+ member _.FileParsed = fileParsed.Publish
+
+ member _.FileChecked = fileChecked.Publish
+
+ member _.ProjectChecked = projectChecked.Publish
+
+ member _.ClearCaches() =
+ use _ = Activity.startNoTags "BackgroundCompiler.ClearCaches"
+
+ lock gate (fun () ->
+ parseCacheLock.AcquireLock(fun ltok ->
+ checkFileInProjectCache.Clear(ltok)
+ parseFileCache.Clear(ltok))
+
+ incrementalBuildersCache.Clear(AnyCallerThread)
+ frameworkTcImportsCache.Clear()
+ scriptClosureCache.Clear AnyCallerThread)
+
+ member _.DownsizeCaches() =
+ use _ = Activity.startNoTags "BackgroundCompiler.DownsizeCaches"
+
+ lock gate (fun () ->
+ parseCacheLock.AcquireLock(fun ltok ->
+ checkFileInProjectCache.Resize(ltok, newKeepStrongly = 1)
+ parseFileCache.Resize(ltok, newKeepStrongly = 1))
+
+ incrementalBuildersCache.Resize(AnyCallerThread, newKeepStrongly = 1, newKeepMax = 1)
+ frameworkTcImportsCache.Downsize()
+ scriptClosureCache.Resize(AnyCallerThread, newKeepStrongly = 1, newKeepMax = 1))
+
+ member _.FrameworkImportsCache = frameworkTcImportsCache
+
+ static member ActualParseFileCount = actualParseFileCount
+
+ static member ActualCheckFileCount = actualCheckFileCount
+
+ interface IBackgroundCompiler with
+
+ member _.BeforeBackgroundFileCheck = self.BeforeBackgroundFileCheck
+
+ member _.CheckFileInProject
+ (
+ parseResults: FSharpParseFileResults,
+ fileName: string,
+ fileVersion: int,
+ sourceText: ISourceText,
+ options: FSharpProjectOptions,
+ userOpName: string
+ ) : NodeCode =
+ self.CheckFileInProject(parseResults, fileName, fileVersion, sourceText, options, userOpName)
+
+ member _.CheckFileInProjectAllowingStaleCachedResults
+ (
+ parseResults: FSharpParseFileResults,
+ fileName: string,
+ fileVersion: int,
+ sourceText: ISourceText,
+ options: FSharpProjectOptions,
+ userOpName: string
+ ) : NodeCode =
+ self.CheckFileInProjectAllowingStaleCachedResults(parseResults, fileName, fileVersion, sourceText, options, userOpName)
+
+ member _.ClearCache(options: seq, userOpName: string) : unit = self.ClearCache(options, userOpName)
+
+ member _.ClearCache(projects: ProjectSnapshot.FSharpProjectIdentifier seq, userOpName: string) = ignore (projects, userOpName)
+
+ member _.ClearCaches() : unit = self.ClearCaches()
+ member _.DownsizeCaches() : unit = self.DownsizeCaches()
+ member _.FileChecked: IEvent = self.FileChecked
+ member _.FileParsed: IEvent = self.FileParsed
+
+ member _.FindReferencesInFile
+ (
+ fileName: string,
+ options: FSharpProjectOptions,
+ symbol: FSharpSymbol,
+ canInvalidateProject: bool,
+ userOpName: string
+ ) : NodeCode> =
+ self.FindReferencesInFile(fileName, options, symbol, canInvalidateProject, userOpName)
+
+ member this.FindReferencesInFile(fileName, projectSnapshot, symbol, userOpName) =
+ this.FindReferencesInFile(fileName, projectSnapshot.ToOptions(), symbol, true, userOpName)
+
+ member _.FrameworkImportsCache: FrameworkImportsCache = self.FrameworkImportsCache
+
+ member _.GetAssemblyData
+ (
+ options: FSharpProjectOptions,
+ _fileName: string,
+ userOpName: string
+ ) : NodeCode =
+ self.GetAssemblyData(options, userOpName)
+
+ member _.GetAssemblyData
+ (
+ projectSnapshot: FSharpProjectSnapshot,
+ _fileName: string,
+ userOpName: string
+ ) : NodeCode =
+ self.GetAssemblyData(projectSnapshot.ToOptions(), userOpName)
+
+ member _.GetBackgroundCheckResultsForFileInProject
+ (
+ fileName: string,
+ options: FSharpProjectOptions,
+ userOpName: string
+ ) : NodeCode =
+ self.GetBackgroundCheckResultsForFileInProject(fileName, options, userOpName)
+
+ member _.GetBackgroundParseResultsForFileInProject
+ (
+ fileName: string,
+ options: FSharpProjectOptions,
+ userOpName: string
+ ) : NodeCode =
+ self.GetBackgroundParseResultsForFileInProject(fileName, options, userOpName)
+
+ member _.GetCachedCheckFileResult
+ (
+ builder: IncrementalBuilder,
+ fileName: string,
+ sourceText: ISourceText,
+ options: FSharpProjectOptions
+ ) : NodeCode<(FSharpParseFileResults * FSharpCheckFileResults) option> =
+ self.GetCachedCheckFileResult(builder, fileName, sourceText, options)
+
+ member _.GetProjectOptionsFromScript
+ (
+ fileName: string,
+ sourceText: ISourceText,
+ previewEnabled: bool option,
+ loadedTimeStamp: DateTime option,
+ otherFlags: string array option,
+ useFsiAuxLib: bool option,
+ useSdkRefs: bool option,
+ sdkDirOverride: string option,
+ assumeDotNetFramework: bool option,
+ optionsStamp: int64 option,
+ userOpName: string
+ ) : Async =
+ self.GetProjectOptionsFromScript(
+ fileName,
+ sourceText,
+ previewEnabled,
+ loadedTimeStamp,
+ otherFlags,
+ useFsiAuxLib,
+ useSdkRefs,
+ sdkDirOverride,
+ assumeDotNetFramework,
+ optionsStamp,
+ userOpName
+ )
+
+ member _.GetSemanticClassificationForFile
+ (
+ fileName: string,
+ options: FSharpProjectOptions,
+ userOpName: string
+ ) : NodeCode =
+ self.GetSemanticClassificationForFile(fileName, options, userOpName)
+
+ member _.GetSemanticClassificationForFile
+ (
+ fileName: string,
+ snapshot: FSharpProjectSnapshot,
+ userOpName: string
+ ) : NodeCode =
+ self.GetSemanticClassificationForFile(fileName, snapshot.ToOptions(), userOpName)
+
+ member _.InvalidateConfiguration(options: FSharpProjectOptions, userOpName: string) : unit =
+ self.InvalidateConfiguration(options, userOpName)
+
+ member _.NotifyFileChanged(fileName: string, options: FSharpProjectOptions, userOpName: string) : NodeCode =
+ self.NotifyFileChanged(fileName, options, userOpName)
+
+ member _.NotifyProjectCleaned(options: FSharpProjectOptions, userOpName: string) : Async =
+ self.NotifyProjectCleaned(options, userOpName)
+
+ member _.ParseAndCheckFileInProject
+ (
+ fileName: string,
+ fileVersion: int,
+ sourceText: ISourceText,
+ options: FSharpProjectOptions,
+ userOpName: string
+ ) : NodeCode =
+ self.ParseAndCheckFileInProject(fileName, fileVersion, sourceText, options, userOpName)
+
+ member _.ParseAndCheckFileInProject
+ (
+ fileName: string,
+ projectSnapshot: FSharpProjectSnapshot,
+ userOpName: string
+ ) : NodeCode =
+ node {
+ let fileSnapshot =
+ projectSnapshot.ProjectSnapshot.SourceFiles
+ |> Seq.find (fun f -> f.FileName = fileName)
+
+ let! sourceText = fileSnapshot.GetSource() |> NodeCode.AwaitTask
+ let options = projectSnapshot.ToOptions()
+
+ return! self.ParseAndCheckFileInProject(fileName, 0, sourceText, options, userOpName)
+ }
+
+ member _.ParseAndCheckProject(options: FSharpProjectOptions, userOpName: string) : NodeCode =
+ self.ParseAndCheckProject(options, userOpName)
+
+ member _.ParseAndCheckProject(projectSnapshot: FSharpProjectSnapshot, userOpName: string) : NodeCode =
+ self.ParseAndCheckProject(projectSnapshot.ToOptions(), userOpName)
+
+ member _.ParseFile
+ (
+ fileName: string,
+ sourceText: ISourceText,
+ options: FSharpParsingOptions,
+ cache: bool,
+ flatErrors: bool,
+ userOpName: string
+ ) =
+ self.ParseFile(fileName, sourceText, options, cache, flatErrors, userOpName)
+
+ member _.ParseFile(fileName: string, projectSnapshot: FSharpProjectSnapshot, userOpName: string) =
+ let options = projectSnapshot.ToOptions()
+
+ self.GetBackgroundParseResultsForFileInProject(fileName, options, userOpName)
+ |> Async.AwaitNodeCode
+
+ member _.ProjectChecked: IEvent = self.ProjectChecked
+
+ member _.TryGetRecentCheckResultsForFile
+ (
+ fileName: string,
+ options: FSharpProjectOptions,
+ sourceText: ISourceText option,
+ userOpName: string
+ ) : (FSharpParseFileResults * FSharpCheckFileResults * SourceTextHash) option =
+ self.TryGetRecentCheckResultsForFile(fileName, options, sourceText, userOpName)
diff --git a/src/Compiler/Service/BackgroundCompiler.fsi b/src/Compiler/Service/BackgroundCompiler.fsi
new file mode 100644
index 00000000000..f3bf3c96ccc
--- /dev/null
+++ b/src/Compiler/Service/BackgroundCompiler.fsi
@@ -0,0 +1,224 @@
+namespace FSharp.Compiler.CodeAnalysis
+
+open FSharp.Compiler.Text
+open FSharp.Compiler.BuildGraph
+
+open System.Reflection
+open FSharp.Compiler.CodeAnalysis
+open FSharp.Compiler.CompilerConfig
+open FSharp.Compiler.Diagnostics
+
+type SourceTextHash = int64
+
+type CacheStamp = int64
+
+type FileName = string
+
+type FilePath = string
+
+type ProjectPath = string
+
+type FileVersion = int
+
+type FSharpProjectSnapshot = ProjectSnapshot.FSharpProjectSnapshot
+
+type internal IBackgroundCompiler =
+
+ /// Type-check the result obtained by parsing. Force the evaluation of the antecedent type checking context if needed.
+ abstract CheckFileInProject:
+ parseResults: FSharpParseFileResults *
+ fileName: string *
+ fileVersion: int *
+ sourceText: ISourceText *
+ options: FSharpProjectOptions *
+ userOpName: string ->
+ NodeCode
+
+ /// Type-check the result obtained by parsing, but only if the antecedent type checking context is available.
+ abstract CheckFileInProjectAllowingStaleCachedResults:
+ parseResults: FSharpParseFileResults *
+ fileName: string *
+ fileVersion: int *
+ sourceText: ISourceText *
+ options: FSharpProjectOptions *
+ userOpName: string ->
+ NodeCode
+
+ abstract ClearCache: options: FSharpProjectOptions seq * userOpName: string -> unit
+
+ abstract ClearCache: projects: ProjectSnapshot.FSharpProjectIdentifier seq * userOpName: string -> unit
+
+ abstract ClearCaches: unit -> unit
+
+ abstract DownsizeCaches: unit -> unit
+
+ abstract FindReferencesInFile:
+ fileName: string *
+ projectSnapshot: FSharpProjectSnapshot *
+ symbol: FSharp.Compiler.Symbols.FSharpSymbol *
+ userOpName: string ->
+ NodeCode
+
+ abstract FindReferencesInFile:
+ fileName: string *
+ options: FSharpProjectOptions *
+ symbol: FSharp.Compiler.Symbols.FSharpSymbol *
+ canInvalidateProject: bool *
+ userOpName: string ->
+ NodeCode
+
+ abstract GetAssemblyData:
+ projectSnapshot: FSharpProjectSnapshot * outputFileName: string * userOpName: string ->
+ NodeCode
+
+ abstract GetAssemblyData:
+ options: FSharpProjectOptions * outputFileName: string * userOpName: string ->
+ NodeCode
+
+ /// Fetch the check information from the background compiler (which checks w.r.t. the FileSystem API)
+ abstract GetBackgroundCheckResultsForFileInProject:
+ fileName: string * options: FSharpProjectOptions * userOpName: string ->
+ NodeCode
+
+ /// Fetch the parse information from the background compiler (which checks w.r.t. the FileSystem API)
+ abstract GetBackgroundParseResultsForFileInProject:
+ fileName: string * options: FSharpProjectOptions * userOpName: string -> NodeCode
+
+ abstract GetCachedCheckFileResult:
+ builder: IncrementalBuilder * fileName: string * sourceText: ISourceText * options: FSharpProjectOptions ->
+ NodeCode<(FSharpParseFileResults * FSharpCheckFileResults) option>
+
+ abstract GetProjectOptionsFromScript:
+ fileName: string *
+ sourceText: ISourceText *
+ previewEnabled: bool option *
+ loadedTimeStamp: System.DateTime option *
+ otherFlags: string array option *
+ useFsiAuxLib: bool option *
+ useSdkRefs: bool option *
+ sdkDirOverride: string option *
+ assumeDotNetFramework: bool option *
+ optionsStamp: int64 option *
+ userOpName: string ->
+ Async
+
+ abstract GetSemanticClassificationForFile:
+ fileName: string * snapshot: FSharpProjectSnapshot * userOpName: string ->
+ NodeCode
+
+ abstract GetSemanticClassificationForFile:
+ fileName: string * options: FSharpProjectOptions * userOpName: string ->
+ NodeCode
+
+ abstract InvalidateConfiguration: options: FSharpProjectOptions * userOpName: string -> unit
+
+ abstract NotifyFileChanged: fileName: string * options: FSharpProjectOptions * userOpName: string -> NodeCode
+
+ abstract NotifyProjectCleaned: options: FSharpProjectOptions * userOpName: string -> Async
+
+ abstract ParseAndCheckFileInProject:
+ fileName: string * projectSnapshot: FSharpProjectSnapshot * userOpName: string ->
+ NodeCode
+
+ /// Parses and checks the source file and returns untyped AST and check results.
+ abstract ParseAndCheckFileInProject:
+ fileName: string *
+ fileVersion: int *
+ sourceText: ISourceText *
+ options: FSharpProjectOptions *
+ userOpName: string ->
+ NodeCode
+
+ abstract ParseAndCheckProject:
+ projectSnapshot: FSharpProjectSnapshot * userOpName: string -> NodeCode
+
+ /// Parse and typecheck the whole project.
+ abstract ParseAndCheckProject:
+ options: FSharpProjectOptions * userOpName: string -> NodeCode
+
+ abstract ParseFile:
+ fileName: string * projectSnapshot: FSharpProjectSnapshot * userOpName: string -> Async
+
+ abstract ParseFile:
+ fileName: string *
+ sourceText: ISourceText *
+ options: FSharpParsingOptions *
+ cache: bool *
+ flatErrors: bool *
+ userOpName: string ->
+ Async
+
+ /// Try to get recent approximate type check results for a file.
+ abstract TryGetRecentCheckResultsForFile:
+ fileName: string * options: FSharpProjectOptions * sourceText: ISourceText option * userOpName: string ->
+ (FSharpParseFileResults * FSharpCheckFileResults * SourceTextHash) option
+
+ abstract BeforeBackgroundFileCheck: IEvent
+
+ abstract FileChecked: IEvent
+
+ abstract FileParsed: IEvent
+
+ abstract FrameworkImportsCache: FrameworkImportsCache
+
+ abstract ProjectChecked: IEvent
+
+[]
+module internal EnvMisc =
+
+ val braceMatchCacheSize: int
+
+ val parseFileCacheSize: int
+
+ val checkFileInProjectCacheSize: int
+
+ val projectCacheSizeDefault: int
+
+ val frameworkTcImportsCacheStrongSize: int
+
+[]
+module internal Helpers =
+
+ /// Determine whether two (fileName,options) keys are identical w.r.t. affect on checking
+ val AreSameForChecking2: (string * FSharpProjectOptions) * (string * FSharpProjectOptions) -> bool
+
+ /// Determine whether two (fileName,options) keys should be identical w.r.t. resource usage
+ val AreSubsumable2: (string * FSharpProjectOptions) * (string * FSharpProjectOptions) -> bool
+
+ /// Determine whether two (fileName,sourceText,options) keys should be identical w.r.t. parsing
+ val AreSameForParsing: (string * int64 * 'a) * (string * int64 * 'a) -> bool when 'a: equality
+
+ val AreSimilarForParsing: ('a * 'b * 'c) * ('a * 'd * 'e) -> bool when 'a: equality
+
+ /// Determine whether two (fileName,sourceText,options) keys should be identical w.r.t. checking
+ val AreSameForChecking3: (string * int64 * FSharpProjectOptions) * (string * int64 * FSharpProjectOptions) -> bool
+
+ /// Determine whether two (fileName,sourceText,options) keys should be identical w.r.t. resource usage
+ val AreSubsumable3: (string * 'a * FSharpProjectOptions) * (string * 'b * FSharpProjectOptions) -> bool
+
+ /// If a symbol is an attribute check if given set of names contains its name without the Attribute suffix
+ val NamesContainAttribute: symbol: FSharp.Compiler.Symbols.FSharpSymbol -> names: Set -> bool
+
+type internal BackgroundCompiler =
+ interface IBackgroundCompiler
+
+ new:
+ legacyReferenceResolver: LegacyReferenceResolver *
+ projectCacheSize: int *
+ keepAssemblyContents: bool *
+ keepAllBackgroundResolutions: bool *
+ tryGetMetadataSnapshot: FSharp.Compiler.AbstractIL.ILBinaryReader.ILReaderTryGetMetadataSnapshot *
+ suggestNamesForErrors: bool *
+ keepAllBackgroundSymbolUses: bool *
+ enableBackgroundItemKeyStoreAndSemanticClassification: bool *
+ enablePartialTypeChecking: bool *
+ parallelReferenceResolution: ParallelReferenceResolution *
+ captureIdentifiersWhenParsing: bool *
+ getSource: (string -> Async) option *
+ useChangeNotifications: bool *
+ useSyntaxTreeCache: bool ->
+ BackgroundCompiler
+
+ static member ActualCheckFileCount: int
+
+ static member ActualParseFileCount: int
diff --git a/src/Compiler/Service/FSharpCheckerResults.fs b/src/Compiler/Service/FSharpCheckerResults.fs
index 37ae0083298..5f18a90968a 100644
--- a/src/Compiler/Service/FSharpCheckerResults.fs
+++ b/src/Compiler/Service/FSharpCheckerResults.fs
@@ -6,6 +6,7 @@
namespace FSharp.Compiler.CodeAnalysis
open System
+open System.Collections.Generic
open System.Diagnostics
open System.IO
open System.Reflection
@@ -53,6 +54,9 @@ open FSharp.Compiler.TypedTreeOps
open Internal.Utilities
open Internal.Utilities.Collections
open FSharp.Compiler.AbstractIL.ILBinaryReader
+open System.Threading.Tasks
+open System.Runtime.CompilerServices
+open Internal.Utilities.Hashing
type FSharpUnresolvedReferencesSet = FSharpUnresolvedReferencesSet of UnresolvedAssemblyReference list
@@ -2515,6 +2519,11 @@ module internal ParseAndCheckFile =
member _.AnyErrors = errorCount > 0
+ member _.CollectedPhasedDiagnostics =
+ [|
+ for struct (diagnostic, severity) in diagnosticsCollector -> diagnostic, severity
+ |]
+
member _.CollectedDiagnostics(symbolEnv: SymbolEnv option) =
[|
for struct (diagnostic, severity) in diagnosticsCollector do
@@ -3270,7 +3279,7 @@ type FSharpCheckFileResults
tcConfig,
tcGlobals,
isIncompleteTypeCheckEnvironment: bool,
- builder: IncrementalBuilder,
+ builder: IncrementalBuilder option,
projectOptions,
dependencyFiles,
creationErrors: FSharpDiagnostic[],
@@ -3311,7 +3320,7 @@ type FSharpCheckFileResults
let errors =
FSharpCheckFileResults.JoinErrors(isIncompleteTypeCheckEnvironment, creationErrors, parseErrors, tcErrors)
- FSharpCheckFileResults(mainInputFileName, errors, Some tcFileInfo, dependencyFiles, Some builder, keepAssemblyContents)
+ FSharpCheckFileResults(mainInputFileName, errors, Some tcFileInfo, dependencyFiles, builder, keepAssemblyContents)
static member CheckOneFile
(
@@ -3328,7 +3337,7 @@ type FSharpCheckFileResults
backgroundDiagnostics: (PhasedDiagnostic * FSharpDiagnosticSeverity)[],
isIncompleteTypeCheckEnvironment: bool,
projectOptions: FSharpProjectOptions,
- builder: IncrementalBuilder,
+ builder: IncrementalBuilder option,
dependencyFiles: string[],
creationErrors: FSharpDiagnostic[],
parseErrors: FSharpDiagnostic[],
@@ -3357,7 +3366,7 @@ type FSharpCheckFileResults
FSharpCheckFileResults.JoinErrors(isIncompleteTypeCheckEnvironment, creationErrors, parseErrors, tcErrors)
let results =
- FSharpCheckFileResults(mainInputFileName, errors, Some tcFileInfo, dependencyFiles, Some builder, keepAssemblyContents)
+ FSharpCheckFileResults(mainInputFileName, errors, Some tcFileInfo, dependencyFiles, builder, keepAssemblyContents)
return results
}
@@ -3375,7 +3384,7 @@ type FSharpCheckProjectResults
TcImports *
CcuThunk *
ModuleOrNamespaceType *
- Choice *
+ Choice> *
TopAttribs option *
(unit -> IRawFSharpAssemblyData option) *
ILAssemblyRef *
@@ -3413,6 +3422,7 @@ type FSharpCheckProjectResults
FSharpAssemblySignature(tcGlobals, thisCcu, ccuSig, tcImports, topAttribs, ccuSig)
+ // TODO: Looks like we don't need this
member _.TypedImplementationFiles =
if not keepAssemblyContents then
invalidOp
@@ -3473,6 +3483,7 @@ type FSharpCheckProjectResults
FSharpAssemblyContents(tcGlobals, thisCcu, Some ccuSig, tcImports, mimpls)
// Not, this does not have to be a SyncOp, it can be called from any thread
+ // TODO: this should be async
member _.GetUsesOfSymbol(symbol: FSharpSymbol, ?cancellationToken: CancellationToken) =
let _, _, _, _, builderOrSymbolUses, _, _, _, _, _, _, _ = getDetails ()
@@ -3488,7 +3499,20 @@ type FSharpCheckProjectResults
| Some(_, tcInfoExtras) -> tcInfoExtras.TcSymbolUses.GetUsesOfSymbol symbol.Item
| _ -> [||]
| _ -> [||])
- | Choice2Of2 tcSymbolUses -> tcSymbolUses.GetUsesOfSymbol symbol.Item
+ |> Array.toSeq
+ | Choice2Of2 task ->
+ Async.RunSynchronously(
+ async {
+ let! tcSymbolUses = task
+
+ return
+ seq {
+ for symbolUses in tcSymbolUses do
+ yield! symbolUses.GetUsesOfSymbol symbol.Item
+ }
+ },
+ ?cancellationToken = cancellationToken
+ )
results
|> Seq.filter (fun symbolUse -> symbolUse.ItemOccurence <> ItemOccurence.RelatedText)
@@ -3500,6 +3524,7 @@ type FSharpCheckProjectResults
|> Seq.toArray
// Not, this does not have to be a SyncOp, it can be called from any thread
+ // TODO: this should be async
member _.GetAllUsesOfAllSymbols(?cancellationToken: CancellationToken) =
let tcGlobals, tcImports, thisCcu, ccuSig, builderOrSymbolUses, _, _, _, _, _, _, _ =
getDetails ()
@@ -3518,7 +3543,8 @@ type FSharpCheckProjectResults
| Some(_, tcInfoExtras) -> tcInfoExtras.TcSymbolUses
| _ -> TcSymbolUses.Empty
| _ -> TcSymbolUses.Empty)
- | Choice2Of2 tcSymbolUses -> [| tcSymbolUses |]
+ |> Array.toSeq
+ | Choice2Of2 tcSymbolUses -> Async.RunSynchronously(tcSymbolUses, ?cancellationToken = cancellationToken)
[|
for r in tcSymbolUses do
@@ -3559,9 +3585,6 @@ type FsiInteractiveChecker(legacyReferenceResolver, tcConfig: TcConfig, tcGlobal
member _.ParseAndCheckInteraction(sourceText: ISourceText, ?userOpName: string) =
cancellable {
- let! ct = Cancellable.token ()
- use _ = Cancellable.UsingToken(ct)
-
let userOpName = defaultArg userOpName "Unknown"
let fileName = Path.Combine(tcConfig.implicitIncludeDir, "stdin.fsx")
let suggestNamesForErrors = true // Will always be true, this is just for readability
@@ -3657,7 +3680,7 @@ type FsiInteractiveChecker(legacyReferenceResolver, tcConfig: TcConfig, tcGlobal
tcImports,
tcFileInfo.ThisCcu,
tcFileInfo.CcuSigForFile,
- Choice2Of2 tcFileInfo.ScopeSymbolUses,
+ Choice2Of2(tcFileInfo.ScopeSymbolUses |> Seq.singleton |> async.Return),
None,
(fun () -> None),
mkSimpleAssemblyRef "stdin",
diff --git a/src/Compiler/Service/FSharpCheckerResults.fsi b/src/Compiler/Service/FSharpCheckerResults.fsi
index 8cdb304c18a..26781c4356e 100644
--- a/src/Compiler/Service/FSharpCheckerResults.fsi
+++ b/src/Compiler/Service/FSharpCheckerResults.fsi
@@ -3,8 +3,10 @@
namespace FSharp.Compiler.CodeAnalysis
open System
+open System.Collections.Generic
open System.IO
open System.Threading
+open System.Threading.Tasks
open Internal.Utilities.Library
open FSharp.Compiler.AbstractIL.IL
open FSharp.Compiler.AbstractIL.ILBinaryReader
@@ -26,6 +28,8 @@ open FSharp.Compiler.TypedTreeOps
open FSharp.Compiler.TcGlobals
open FSharp.Compiler.Text
+open Internal.Utilities.Collections
+
/// Delays the creation of an ILModuleReader
[]
type DelayedILModuleReader =
@@ -443,7 +447,7 @@ type public FSharpCheckFileResults =
tcConfig: TcConfig *
tcGlobals: TcGlobals *
isIncompleteTypeCheckEnvironment: bool *
- builder: IncrementalBuilder *
+ builder: IncrementalBuilder option *
projectOptions: FSharpProjectOptions *
dependencyFiles: string[] *
creationErrors: FSharpDiagnostic[] *
@@ -477,7 +481,7 @@ type public FSharpCheckFileResults =
backgroundDiagnostics: (PhasedDiagnostic * FSharpDiagnosticSeverity)[] *
isIncompleteTypeCheckEnvironment: bool *
projectOptions: FSharpProjectOptions *
- builder: IncrementalBuilder *
+ builder: IncrementalBuilder option *
dependencyFiles: string[] *
creationErrors: FSharpDiagnostic[] *
parseErrors: FSharpDiagnostic[] *
@@ -537,7 +541,7 @@ type public FSharpCheckProjectResults =
TcImports *
CcuThunk *
ModuleOrNamespaceType *
- Choice *
+ Choice> *
TopAttribs option *
(unit -> IRawFSharpAssemblyData option) *
ILAssemblyRef *
@@ -569,6 +573,29 @@ module internal ParseAndCheckFile =
ct: CancellationToken ->
(range * range)[]
+ /// Diagnostics handler for parsing & type checking while processing a single file
+ type DiagnosticsHandler =
+ new:
+ reportErrors: bool *
+ mainInputFileName: string *
+ diagnosticsOptions: FSharpDiagnosticOptions *
+ sourceText: ISourceText *
+ suggestNamesForErrors: bool *
+ flatErrors: bool ->
+ DiagnosticsHandler
+
+ member DiagnosticsLogger: DiagnosticsLogger
+
+ member ErrorCount: int
+
+ member DiagnosticOptions: FSharpDiagnosticOptions with set
+
+ member AnyErrors: bool
+
+ member CollectedPhasedDiagnostics: (PhasedDiagnostic * FSharpDiagnosticSeverity) array
+
+ member CollectedDiagnostics: symbolEnv: SymbolEnv option -> FSharpDiagnostic array
+
// An object to typecheck source in a given typechecking environment.
// Used internally to provide intellisense over F# Interactive.
type internal FsiInteractiveChecker =
diff --git a/src/Compiler/Service/FSharpParseFileResults.fs b/src/Compiler/Service/FSharpParseFileResults.fs
index 52a140195ce..baea32da816 100644
--- a/src/Compiler/Service/FSharpParseFileResults.fs
+++ b/src/Compiler/Service/FSharpParseFileResults.fs
@@ -6,7 +6,6 @@ open System
open System.IO
open System.Collections.Generic
open System.Diagnostics
-open Internal.Utilities.Library
open FSharp.Compiler.Diagnostics
open FSharp.Compiler.EditorServices
open FSharp.Compiler.Syntax
@@ -115,201 +114,118 @@ type FSharpParseFileResults(diagnostics: FSharpDiagnostic[], input: ParsedInput,
| _ -> Some workingRange
- let visitor =
- { new SyntaxVisitorBase<_>() with
- override _.VisitExpr(_, _, defaultTraverse, expr) = defaultTraverse expr
-
- override _.VisitBinding(_path, defaultTraverse, binding) =
- match binding with
- | SynBinding(valData = SynValData(memberFlags = None); expr = expr) as b when
- rangeContainsPos b.RangeOfBindingWithRhs pos
- ->
- match tryGetIdentRangeFromBinding b with
- | Some range -> walkBinding expr range
- | None -> None
- | _ -> defaultTraverse binding
- }
-
- SyntaxTraversal.Traverse(pos, input, visitor)
+ (pos, input)
+ ||> ParsedInput.tryPick (fun _path node ->
+ match node with
+ | SyntaxNode.SynBinding(SynBinding(valData = SynValData(memberFlags = None); expr = expr) as b) when
+ rangeContainsPos b.RangeOfBindingWithRhs pos
+ ->
+ match tryGetIdentRangeFromBinding b with
+ | Some range -> walkBinding expr range
+ | None -> None
+ | _ -> None)
member _.TryIdentOfPipelineContainingPosAndNumArgsApplied pos =
- let visitor =
- { new SyntaxVisitorBase<_>() with
- member _.VisitExpr(_, _, defaultTraverse, expr) =
- match expr with
- | SynExpr.App(_, _, SynExpr.App(_, true, SynExpr.LongIdent(longDotId = SynLongIdent(id = [ ident ])), _, _), argExpr, _) when
- rangeContainsPos argExpr.Range pos
- ->
- match argExpr with
- | SynExpr.App(_, _, _, SynExpr.Paren(expr, _, _, _), _) when rangeContainsPos expr.Range pos -> None
- | _ ->
- if ident.idText = "op_PipeRight" then Some(ident, 1)
- elif ident.idText = "op_PipeRight2" then Some(ident, 2)
- elif ident.idText = "op_PipeRight3" then Some(ident, 3)
- else None
- | _ -> defaultTraverse expr
- }
-
- SyntaxTraversal.Traverse(pos, input, visitor)
+ (pos, input)
+ ||> ParsedInput.tryPick (fun _path node ->
+ match node with
+ | SyntaxNode.SynExpr(SynExpr.App(
+ funcExpr = SynExpr.App(_, true, SynExpr.LongIdent(longDotId = SynLongIdent(id = [ ident ])), _, _); argExpr = argExpr)) when
+ rangeContainsPos argExpr.Range pos
+ ->
+ match argExpr with
+ | SynExpr.App(_, _, _, SynExpr.Paren(expr, _, _, _), _) when rangeContainsPos expr.Range pos -> None
+ | _ ->
+ if ident.idText = "op_PipeRight" then Some(ident, 1)
+ elif ident.idText = "op_PipeRight2" then Some(ident, 2)
+ elif ident.idText = "op_PipeRight3" then Some(ident, 3)
+ else None
+ | _ -> None)
member _.IsPosContainedInApplication pos =
- let visitor =
- { new SyntaxVisitorBase<_>() with
- member _.VisitExpr(_, traverseSynExpr, defaultTraverse, expr) =
- match expr with
- | SynExpr.TypeApp(_, _, _, _, _, _, range) when rangeContainsPos range pos -> Some range
- | SynExpr.App(_, _, _, SynExpr.ComputationExpr(_, expr, _), range) when rangeContainsPos range pos ->
- traverseSynExpr expr
- | SynExpr.App(_, _, _, _, range) when rangeContainsPos range pos -> Some range
- | _ -> defaultTraverse expr
- }
-
- let result = SyntaxTraversal.Traverse(pos, input, visitor)
- result.IsSome
+ (pos, input)
+ ||> ParsedInput.exists (fun _path node ->
+ match node with
+ | SyntaxNode.SynExpr(SynExpr.App(argExpr = SynExpr.ComputationExpr _) | SynExpr.TypeApp(expr = SynExpr.ComputationExpr _)) ->
+ false
+ | SyntaxNode.SynExpr(SynExpr.App(range = range) | SynExpr.TypeApp(range = range)) when rangeContainsPos range pos -> true
+ | _ -> false)
member _.IsTypeName(range: range) =
- let visitor =
- { new SyntaxVisitorBase<_>() with
- member _.VisitModuleDecl(_, _, synModuleDecl) =
- match synModuleDecl with
- | SynModuleDecl.Types(typeDefns, _) ->
- typeDefns
- |> Seq.exists (fun (SynTypeDefn(typeInfo, _, _, _, _, _)) -> typeInfo.Range = range)
- |> Some
- | _ -> None
- }
-
- let result = SyntaxTraversal.Traverse(range.Start, input, visitor)
- result |> Option.contains true
+ (range.Start, input)
+ ||> ParsedInput.exists (fun _path node ->
+ match node with
+ | SyntaxNode.SynTypeDefn(SynTypeDefn(typeInfo = typeInfo)) -> typeInfo.Range = range
+ | _ -> false)
member _.TryRangeOfFunctionOrMethodBeingApplied pos =
- let rec getIdentRangeForFuncExprInApp traverseSynExpr expr pos =
- match expr with
- | SynExpr.Ident ident -> Some ident.idRange
-
- | SynExpr.LongIdent(_, _, _, range) -> Some range
-
- | SynExpr.Paren(expr, _, _, range) when rangeContainsPos range pos -> getIdentRangeForFuncExprInApp traverseSynExpr expr pos
-
- | SynExpr.TypeApp(expr, _, _, _, _, _, _) -> getIdentRangeForFuncExprInApp traverseSynExpr expr pos
-
- | SynExpr.App(_, _, funcExpr, argExpr, _) ->
- match argExpr with
- | SynExpr.App(_, _, _, _, range) when rangeContainsPos range pos ->
- getIdentRangeForFuncExprInApp traverseSynExpr argExpr pos
-
- // Special case: `async { ... }` is actually a ComputationExpr inside of the argExpr of a SynExpr.App
- | SynExpr.ComputationExpr(_, expr, range)
- | SynExpr.Paren(expr, _, _, range) when rangeContainsPos range pos -> getIdentRangeForFuncExprInApp traverseSynExpr expr pos
-
- // Yielding values in an array or list that is used as an argument: List.sum [ getVal a b; getVal b c ]
- | SynExpr.ArrayOrListComputed(_, expr, range) when rangeContainsPos range pos ->
- if rangeContainsPos expr.Range pos then
- getIdentRangeForFuncExprInApp traverseSynExpr expr pos
- else
- (*
- In cases like
-
- let test () = div [] [
- str ""
- ; |
- ]
-
- `ProvideParametersAsyncAux` currently works with the wrong symbol or
- doesn't detect the previously applied arguments.
- Until that is fixed, don't show any tooltips rather than the wrong signature.
- *)
- None
-
- | _ ->
- match funcExpr with
- | SynExpr.App(_, true, _, _, _) when rangeContainsPos argExpr.Range pos ->
- // x |> List.map
- // Don't dive into the funcExpr (the operator expr)
- // because we dont want to offer sig help for that!
- getIdentRangeForFuncExprInApp traverseSynExpr argExpr pos
- | _ ->
- // Generally, we want to dive into the func expr to get the range
- // of the identifier of the function we're after
- getIdentRangeForFuncExprInApp traverseSynExpr funcExpr pos
-
- | SynExpr.Sequential(_, _, expr1, expr2, range) when rangeContainsPos range pos ->
- if rangeContainsPos expr1.Range pos then
- getIdentRangeForFuncExprInApp traverseSynExpr expr1 pos
- else
- getIdentRangeForFuncExprInApp traverseSynExpr expr2 pos
-
- | SynExpr.LetOrUse(bindings = bindings; body = body; range = range) when rangeContainsPos range pos ->
- let binding =
- bindings |> List.tryFind (fun x -> rangeContainsPos x.RangeOfBindingWithRhs pos)
-
- match binding with
- | Some(SynBinding.SynBinding(expr = expr)) -> getIdentRangeForFuncExprInApp traverseSynExpr expr pos
- | None -> getIdentRangeForFuncExprInApp traverseSynExpr body pos
-
- | SynExpr.IfThenElse(ifExpr = ifExpr; thenExpr = thenExpr; elseExpr = elseExpr; range = range) when rangeContainsPos range pos ->
- if rangeContainsPos ifExpr.Range pos then
- getIdentRangeForFuncExprInApp traverseSynExpr ifExpr pos
- elif rangeContainsPos thenExpr.Range pos then
- getIdentRangeForFuncExprInApp traverseSynExpr thenExpr pos
- else
- match elseExpr with
- | None -> None
- | Some expr -> getIdentRangeForFuncExprInApp traverseSynExpr expr pos
+ let rec (|FuncIdent|_|) (node, path) =
+ match node, path with
+ | SyntaxNode.SynExpr(DeepestIdentifiedFuncInAppChain range), _ -> Some range
+ | SyntaxNode.SynExpr PossibleBareArg, DeepestIdentifiedFuncInPath range -> Some range
+ | SyntaxNode.SynExpr(Identifier range), _ -> Some range
+ | _ -> None
- | SynExpr.Match(expr = expr; clauses = clauses; range = range) when rangeContainsPos range pos ->
+ and (|DeepestIdentifiedFuncInAppChain|_|) expr =
+ let (|Contains|_|) pos (expr: SynExpr) =
if rangeContainsPos expr.Range pos then
- getIdentRangeForFuncExprInApp traverseSynExpr expr pos
+ Some Contains
else
- let clause =
- clauses |> List.tryFind (fun clause -> rangeContainsPos clause.Range pos)
-
- match clause with
- | None -> None
- | Some clause ->
- match clause with
- | SynMatchClause.SynMatchClause(whenExpr = whenExprOpt; resultExpr = resultExpr) ->
- match whenExprOpt with
- | None -> getIdentRangeForFuncExprInApp traverseSynExpr resultExpr pos
- | Some whenExpr ->
- if rangeContainsPos whenExpr.Range pos then
- getIdentRangeForFuncExprInApp traverseSynExpr whenExpr pos
- else
- getIdentRangeForFuncExprInApp traverseSynExpr resultExpr pos
-
- // Ex: C.M(x, y, ...) <--- We want to find where in the tupled application the call is being made
- | SynExpr.Tuple(_, exprs, _, tupRange) when rangeContainsPos tupRange pos ->
- let expr = exprs |> List.tryFind (fun expr -> rangeContainsPos expr.Range pos)
-
- match expr with
- | None -> None
- | Some expr -> getIdentRangeForFuncExprInApp traverseSynExpr expr pos
-
- // Capture the body of a lambda, often nested in a call to a collection function
- | SynExpr.Lambda(body = body) when rangeContainsPos body.Range pos -> getIdentRangeForFuncExprInApp traverseSynExpr body pos
+ None
- | SynExpr.DotLambda(expr = body) when rangeContainsPos body.Range pos -> getIdentRangeForFuncExprInApp traverseSynExpr body pos
-
- | SynExpr.Do(expr, range) when rangeContainsPos range pos -> getIdentRangeForFuncExprInApp traverseSynExpr expr pos
+ match expr with
+ | SynExpr.App(argExpr = Contains pos & DeepestIdentifiedFuncInAppChain range) -> Some range
+ | SynExpr.App(isInfix = false; funcExpr = Identifier range | DeepestIdentifiedFuncInAppChain range) -> Some range
+ | SynExpr.TypeApp(expr = Identifier range) -> Some range
+ | SynExpr.Paren(expr = Contains pos & DeepestIdentifiedFuncInAppChain range) -> Some range
+ | _ -> None
- | SynExpr.Assert(expr, range) when rangeContainsPos range pos -> getIdentRangeForFuncExprInApp traverseSynExpr expr pos
+ and (|DeepestIdentifiedFuncInPath|_|) path =
+ match path with
+ | SyntaxNode.SynExpr(DeepestIdentifiedFuncInAppChain range) :: _
+ | SyntaxNode.SynExpr PossibleBareArg :: DeepestIdentifiedFuncInPath range -> Some range
+ | _ -> None
- | SynExpr.ArbitraryAfterError(_debugStr, range) when rangeContainsPos range pos -> Some range
+ and (|Identifier|_|) expr =
+ let (|Ident|) (ident: Ident) = ident.idRange
- | expr -> traverseSynExpr expr
+ match expr with
+ | SynExpr.Ident(ident = Ident range)
+ | SynExpr.LongIdent(range = range)
+ | SynExpr.ArbitraryAfterError(range = range) -> Some range
+ | _ -> None
- let visitor =
- { new SyntaxVisitorBase<_>() with
- member _.VisitExpr(_, traverseSynExpr, defaultTraverse, expr) =
- match expr with
- | SynExpr.TypeApp(expr, _, _, _, _, _, range) when rangeContainsPos range pos ->
- getIdentRangeForFuncExprInApp traverseSynExpr expr pos
- | SynExpr.App(_, _, _funcExpr, _, range) as app when rangeContainsPos range pos ->
- getIdentRangeForFuncExprInApp traverseSynExpr app pos
- | _ -> defaultTraverse expr
- }
+ and (|PossibleBareArg|_|) expr =
+ match expr with
+ | SynExpr.App _
+ | SynExpr.TypeApp _
+ | SynExpr.Ident _
+ | SynExpr.LongIdent _
+ | SynExpr.Const _
+ | SynExpr.Null _
+ | SynExpr.InterpolatedString _ -> Some PossibleBareArg
+
+ // f (g ‸)
+ | SynExpr.Paren(expr = SynExpr.Ident _ | SynExpr.LongIdent _; range = parenRange) when
+ rangeContainsPos parenRange pos
+ && not (expr.Range.End.IsAdjacentTo parenRange.End)
+ ->
+ None
+
+ | SynExpr.Paren _ -> Some PossibleBareArg
+ | _ -> None
- SyntaxTraversal.Traverse(pos, input, visitor)
+ match input |> ParsedInput.tryNode pos with
+ | Some(FuncIdent range) -> Some range
+ | Some _ -> None
+ | None ->
+ // The cursor is outside any existing node's range,
+ // so try to drill down into the nearest one.
+ (pos, input)
+ ||> ParsedInput.tryPickLast (fun path node ->
+ match node, path with
+ | FuncIdent range -> Some range
+ | _ -> None)
member _.GetAllArgumentsForFunctionApplicationAtPosition pos =
SynExprAppLocationsImpl.getAllCurriedArgsAtPosition pos input
@@ -326,248 +242,161 @@ type FSharpParseFileResults(diagnostics: FSharpDiagnostic[], input: ParsedInput,
false,
SynExpr.App(ExprAtomicFlag.NonAtomic, true, Ident "op_EqualsGreater", actualParamListExpr, _),
actualLambdaBodyExpr,
- _) -> Some(actualParamListExpr, actualLambdaBodyExpr)
+ range) -> Some(range, actualParamListExpr, actualLambdaBodyExpr)
| _ -> None
- SyntaxTraversal.Traverse(
- opGreaterEqualPos,
- input,
- { new SyntaxVisitorBase<_>() with
- member _.VisitExpr(_, _, defaultTraverse, expr) =
- match expr with
- | SynExpr.Paren(InfixAppOfOpEqualsGreater(lambdaArgs, lambdaBody) as app, _, _, _) ->
- Some(app.Range, lambdaArgs.Range, lambdaBody.Range)
- | _ -> defaultTraverse expr
-
- member _.VisitBinding(_path, defaultTraverse, binding) =
- match binding with
- | SynBinding(kind = SynBindingKind.Normal; expr = InfixAppOfOpEqualsGreater(lambdaArgs, lambdaBody) as app) ->
- Some(app.Range, lambdaArgs.Range, lambdaBody.Range)
- | _ -> defaultTraverse binding
- }
- )
+ (opGreaterEqualPos, input)
+ ||> ParsedInput.tryPick (fun _path node ->
+ match node with
+ | SyntaxNode.SynExpr(SynExpr.Paren(expr = InfixAppOfOpEqualsGreater(range, lambdaArgs, lambdaBody)))
+ | SyntaxNode.SynBinding(SynBinding(
+ kind = SynBindingKind.Normal; expr = InfixAppOfOpEqualsGreater(range, lambdaArgs, lambdaBody))) ->
+ Some(range, lambdaArgs.Range, lambdaBody.Range)
+ | _ -> None)
member _.TryRangeOfStringInterpolationContainingPos pos =
- let visitor =
- { new SyntaxVisitorBase<_>() with
- member _.VisitExpr(_, _, defaultTraverse, expr) =
- match expr with
- | SynExpr.InterpolatedString(range = range) when rangeContainsPos range pos -> Some range
- | _ -> defaultTraverse expr
- }
-
- SyntaxTraversal.Traverse(pos, input, visitor)
+ (pos, input)
+ ||> ParsedInput.tryPick (fun _path node ->
+ match node with
+ | SyntaxNode.SynExpr(SynExpr.InterpolatedString(range = range)) when rangeContainsPos range pos -> Some range
+ | _ -> None)
member _.TryRangeOfExprInYieldOrReturn pos =
- let visitor =
- { new SyntaxVisitorBase<_>() with
- member _.VisitExpr(_path, _, defaultTraverse, expr) =
- match expr with
- | SynExpr.YieldOrReturn(_, expr, range)
- | SynExpr.YieldOrReturnFrom(_, expr, range) when rangeContainsPos range pos -> Some expr.Range
- | _ -> defaultTraverse expr
- }
-
- SyntaxTraversal.Traverse(pos, input, visitor)
+ (pos, input)
+ ||> ParsedInput.tryPick (fun _path node ->
+ match node with
+ | SyntaxNode.SynExpr(SynExpr.YieldOrReturn(expr = expr; range = range) | SynExpr.YieldOrReturnFrom(expr = expr; range = range)) when
+ rangeContainsPos range pos
+ ->
+ Some expr.Range
+ | _ -> None)
member _.TryRangeOfRecordExpressionContainingPos pos =
- let visitor =
- { new SyntaxVisitorBase<_>() with
- member _.VisitExpr(_, _, defaultTraverse, expr) =
- match expr with
- | SynExpr.Record(_, _, _, range) when rangeContainsPos range pos -> Some range
- | _ -> defaultTraverse expr
- }
-
- SyntaxTraversal.Traverse(pos, input, visitor)
+ (pos, input)
+ ||> ParsedInput.tryPick (fun _path node ->
+ match node with
+ | SyntaxNode.SynExpr(SynExpr.Record(range = range)) when rangeContainsPos range pos -> Some range
+ | _ -> None)
member _.TryRangeOfRefCellDereferenceContainingPos expressionPos =
- let visitor =
- { new SyntaxVisitorBase<_>() with
- member _.VisitExpr(_, _, defaultTraverse, expr) =
- match expr with
- | SynExpr.App(_, false, SynExpr.LongIdent(longDotId = SynLongIdent(id = [ funcIdent ])), expr, _) ->
- if funcIdent.idText = "op_Dereference" && rangeContainsPos expr.Range expressionPos then
- Some funcIdent.idRange
- else
- None
- | _ -> defaultTraverse expr
- }
-
- SyntaxTraversal.Traverse(expressionPos, input, visitor)
+ (expressionPos, input)
+ ||> ParsedInput.tryPick (fun _path node ->
+ let (|Ident|) (ident: Ident) = ident.idText
+
+ match node with
+ | SyntaxNode.SynExpr(SynExpr.App(
+ isInfix = false
+ funcExpr = SynExpr.LongIdent(longDotId = SynLongIdent(id = [ funcIdent & Ident "op_Dereference" ]))
+ argExpr = argExpr)) when rangeContainsPos argExpr.Range expressionPos -> Some funcIdent.idRange
+ | _ -> None)
member _.TryRangeOfExpressionBeingDereferencedContainingPos expressionPos =
- let visitor =
- { new SyntaxVisitorBase<_>() with
- member _.VisitExpr(_, _, defaultTraverse, expr) =
- match expr with
- | SynExpr.App(_, false, SynExpr.LongIdent(longDotId = SynLongIdent(id = [ funcIdent ])), expr, _) ->
- if funcIdent.idText = "op_Dereference" && rangeContainsPos expr.Range expressionPos then
- Some expr.Range
- else
- None
- | _ -> defaultTraverse expr
- }
-
- SyntaxTraversal.Traverse(expressionPos, input, visitor)
+ (expressionPos, input)
+ ||> ParsedInput.tryPick (fun _path node ->
+ let (|Ident|) (ident: Ident) = ident.idText
+
+ match node with
+ | SyntaxNode.SynExpr(SynExpr.App(
+ isInfix = false; funcExpr = SynExpr.LongIdent(longDotId = SynLongIdent(id = [ Ident "op_Dereference" ])); argExpr = argExpr)) when
+ rangeContainsPos argExpr.Range expressionPos
+ ->
+ Some argExpr.Range
+ | _ -> None)
member _.TryRangeOfReturnTypeHint(symbolUseStart: pos, ?skipLambdas) =
let skipLambdas = defaultArg skipLambdas true
- SyntaxTraversal.Traverse(
- symbolUseStart,
- input,
- { new SyntaxVisitorBase<_>() with
- member _.VisitExpr(_path, _traverseSynExpr, defaultTraverse, expr) = defaultTraverse expr
-
- override _.VisitBinding(_path, defaultTraverse, binding) =
- match binding with
- | SynBinding(expr = SynExpr.Lambda _) when skipLambdas -> defaultTraverse binding
- | SynBinding(expr = SynExpr.DotLambda _) when skipLambdas -> defaultTraverse binding
+ (symbolUseStart, input)
+ ||> ParsedInput.tryPick (fun _path node ->
+ match node with
+ | SyntaxNode.SynBinding(SynBinding(expr = SynExpr.Lambda _))
+ | SyntaxNode.SynBinding(SynBinding(expr = SynExpr.DotLambda _)) when skipLambdas -> None
- // Skip manually type-annotated bindings
- | SynBinding(returnInfo = Some(SynBindingReturnInfo _)) -> defaultTraverse binding
+ // Skip manually type-annotated bindings
+ | SyntaxNode.SynBinding(SynBinding(returnInfo = Some(SynBindingReturnInfo _))) -> None
- // Let binding
- | SynBinding(trivia = { EqualsRange = Some equalsRange }; range = range) when range.Start = symbolUseStart ->
- Some equalsRange.StartRange
+ // Let binding
+ | SyntaxNode.SynBinding(SynBinding(trivia = { EqualsRange = Some equalsRange }; range = range)) when
+ range.Start = symbolUseStart
+ ->
+ Some equalsRange.StartRange
- // Member binding
- | SynBinding(
- headPat = SynPat.LongIdent(longDotId = SynLongIdent(id = _ :: ident :: _))
- trivia = { EqualsRange = Some equalsRange }) when ident.idRange.Start = symbolUseStart ->
- Some equalsRange.StartRange
+ // Member binding
+ | SyntaxNode.SynBinding(SynBinding(
+ headPat = SynPat.LongIdent(longDotId = SynLongIdent(id = _ :: ident :: _)); trivia = { EqualsRange = Some equalsRange })) when
+ ident.idRange.Start = symbolUseStart
+ ->
+ Some equalsRange.StartRange
- | _ -> defaultTraverse binding
- }
- )
+ | _ -> None)
member _.FindParameterLocations pos = ParameterLocations.Find(pos, input)
member _.IsPositionContainedInACurriedParameter pos =
- let visitor =
- { new SyntaxVisitorBase<_>() with
- member _.VisitExpr(_path, traverseSynExpr, defaultTraverse, expr) = defaultTraverse (expr)
-
- override _.VisitBinding(_path, _, binding) =
- match binding with
- | SynBinding(valData = valData; range = range) when rangeContainsPos range pos ->
- let info = valData.SynValInfo.CurriedArgInfos
- let mutable found = false
-
- for group in info do
- for arg in group do
- match arg.Ident with
- | Some ident when rangeContainsPos ident.idRange pos -> found <- true
- | _ -> ()
-
- if found then Some range else None
- | _ -> None
- }
-
- let result = SyntaxTraversal.Traverse(pos, input, visitor)
- result.IsSome
+ (pos, input)
+ ||> ParsedInput.exists (fun _path node ->
+ match node with
+ | SyntaxNode.SynBinding(SynBinding(valData = valData; range = range)) when rangeContainsPos range pos ->
+ valData.SynValInfo.CurriedArgInfos
+ |> List.exists (
+ List.exists (function
+ | SynArgInfo(ident = Some ident) -> rangeContainsPos ident.idRange pos
+ | _ -> false)
+ )
+
+ | _ -> false)
member _.IsTypeAnnotationGivenAtPosition pos =
- let visitor =
- { new SyntaxVisitorBase<_>() with
- member _.VisitExpr(_path, _traverseSynExpr, defaultTraverse, expr) =
- match expr with
- | SynExpr.Typed(_expr, _typeExpr, range) when Position.posEq range.Start pos -> Some range
- | _ -> defaultTraverse expr
-
- override _.VisitSimplePats(_path, pat) =
- let rec loop (pat: SynPat) =
- if not (rangeContainsPos pat.Range pos) then
- None
- else
-
- match pat with
- | SynPat.Attrib(pat = pat)
- | SynPat.Paren(pat = pat) -> loop pat
-
- | SynPat.Tuple(elementPats = pats) -> List.tryPick loop pats
-
- | SynPat.Typed(range = range) when Position.posEq range.Start pos -> Some pat.Range
-
- | _ -> None
-
- loop pat
+ (pos, input)
+ ||> ParsedInput.exists (fun _path node ->
+ let rec (|Typed|_|) (pat: SynPat) =
+ if not (rangeContainsPos pat.Range pos) then
+ None
+ else
+ let (|AnyTyped|_|) = List.tryPick (|Typed|_|)
- override _.VisitPat(_path, defaultTraverse, pat) =
- // (s: string)
match pat with
- | SynPat.Typed(_pat, _targetType, range) when Position.posEq range.Start pos -> Some range
- | _ -> defaultTraverse pat
-
- override _.VisitBinding(_path, defaultTraverse, binding) =
- // let x : int = 12
- match binding with
- | SynBinding(
- headPat = SynPat.Named(range = patRange); returnInfo = Some(SynBindingReturnInfo(typeName = SynType.LongIdent _))) ->
- Some patRange
- | _ -> defaultTraverse binding
- }
+ | SynPat.Typed(range = range) when Position.posEq range.Start pos -> Some Typed
+ | SynPat.Paren(pat = Typed) -> Some Typed
+ | SynPat.Tuple(elementPats = AnyTyped) -> Some Typed
+ | _ -> None
- let result = SyntaxTraversal.Traverse(pos, input, visitor)
- result.IsSome
+ match node with
+ | SyntaxNode.SynExpr(SynExpr.Typed(range = range))
+ | SyntaxNode.SynPat(SynPat.Typed(range = range)) -> Position.posEq range.Start pos
+ | SyntaxNode.SynTypeDefn(SynTypeDefn(implicitConstructor = Some(SynMemberDefn.ImplicitCtor(ctorArgs = Typed))))
+ | SyntaxNode.SynBinding(SynBinding(
+ headPat = SynPat.Named _; returnInfo = Some(SynBindingReturnInfo(typeName = SynType.LongIdent _)))) -> true
+ | _ -> false)
member _.IsPositionWithinTypeDefinition pos =
- let visitor =
- { new SyntaxVisitorBase<_>() with
- override _.VisitComponentInfo(path, _) =
- let typeDefs =
- path
- |> List.filter (function
- | SyntaxNode.SynModule(SynModuleDecl.Types _) -> true
- | _ -> false)
-
- match typeDefs with
- | [] -> None
- | _ -> Some true
- }
-
- let result = SyntaxTraversal.Traverse(pos, input, visitor)
- result.IsSome
+ (pos, input)
+ ||> ParsedInput.exists (fun _path node ->
+ match node with
+ | SyntaxNode.SynTypeDefn _ -> true
+ | _ -> false)
member _.IsBindingALambdaAtPosition pos =
- let visitor =
- { new SyntaxVisitorBase<_>() with
- member _.VisitExpr(_path, _traverseSynExpr, defaultTraverse, expr) = defaultTraverse expr
-
- override _.VisitBinding(_path, defaultTraverse, binding) =
- match binding with
- | SynBinding.SynBinding(expr = expr; range = range) when Position.posEq range.Start pos ->
- match expr with
- | SynExpr.Lambda _ -> Some range
- | SynExpr.DotLambda _ -> Some range
- | _ -> None
- | _ -> defaultTraverse binding
- }
-
- let result = SyntaxTraversal.Traverse(pos, input, visitor)
- result.IsSome
+ (pos, input)
+ ||> ParsedInput.exists (fun _path node ->
+ match node with
+ | SyntaxNode.SynBinding(SynBinding(expr = SynExpr.Lambda _; range = range))
+ | SyntaxNode.SynBinding(SynBinding(expr = SynExpr.DotLambda _; range = range)) -> Position.posEq range.Start pos
+ | _ -> false)
member _.IsPositionWithinRecordDefinition pos =
let isWithin left right middle =
Position.posGt right left && Position.posLt middle right
- let visitor =
- { new SyntaxVisitorBase<_>() with
- override _.VisitRecordDefn(_, _, range) =
- if pos |> isWithin range.Start range.End then
- Some true
- else
- None
-
- override _.VisitTypeAbbrev(_, synType, range) =
- match synType with
- | SynType.AnonRecd _ when pos |> isWithin range.Start range.End -> Some true
- | _ -> None
- }
-
- let result = SyntaxTraversal.Traverse(pos, input, visitor)
- result.IsSome
+ (pos, input)
+ ||> ParsedInput.exists (fun _path node ->
+ match node with
+ | SyntaxNode.SynTypeDefn(SynTypeDefn(typeRepr = SynTypeDefnRepr.Simple(SynTypeDefnSimpleRepr.Record _, range)))
+ | SyntaxNode.SynTypeDefn(SynTypeDefn(typeRepr = SynTypeDefnRepr.Simple(SynTypeDefnSimpleRepr.TypeAbbrev _, range))) when
+ pos |> isWithin range.Start range.End
+ ->
+ true
+ | _ -> false)
/// Get declared items and the selected item at the specified location
member _.GetNavigationItemsImpl() =
diff --git a/src/Compiler/Service/FSharpProjectSnapshot.fs b/src/Compiler/Service/FSharpProjectSnapshot.fs
new file mode 100644
index 00000000000..259948dc706
--- /dev/null
+++ b/src/Compiler/Service/FSharpProjectSnapshot.fs
@@ -0,0 +1,626 @@
+// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information.
+
+module FSharp.Compiler.CodeAnalysis.ProjectSnapshot
+
+open System
+open System.Collections.Generic
+open System.IO
+open System.Reflection
+open FSharp.Compiler.IO
+open Internal.Utilities.Library.Extras
+open FSharp.Core.Printf
+open FSharp.Compiler.Text
+
+open Internal.Utilities.Collections
+open System.Threading.Tasks
+open Internal.Utilities.Hashing
+open System.Collections.Immutable
+open System.Runtime.CompilerServices
+open FSharp.Compiler.Syntax
+open FSharp.Compiler.Diagnostics
+open FSharp.Compiler.DiagnosticsLogger
+
+type internal ProjectIdentifier = string * string
+
+/// A common interface for an F# source file snapshot that can be used accross all stages (lazy, source loaded, parsed)
+type internal IFileSnapshot =
+ abstract member FileName: string
+ abstract member Version: byte array
+ abstract member IsSignatureFile: bool
+
+[]
+module internal Helpers =
+
+ let isSignatureFile (fileName: string) =
+ // TODO: is this robust enough?
+ fileName[fileName.Length - 1] = 'i'
+
+ let addFileName (file: IFileSnapshot) = Md5Hasher.addString file.FileName
+
+ let addFileNameAndVersion (file: IFileSnapshot) =
+ addFileName file >> Md5Hasher.addBytes file.Version
+
+ let signatureHash projectCoreVersion (sourceFiles: IFileSnapshot seq) =
+ let mutable lastFile = ""
+
+ ((projectCoreVersion, Set.empty), sourceFiles)
+ ||> Seq.fold (fun (res, sigs) file ->
+ if file.IsSignatureFile then
+ lastFile <- file.FileName
+ res |> addFileNameAndVersion file, sigs |> Set.add file.FileName
+ else
+ let sigFileName = $"{file.FileName}i"
+
+ if sigs.Contains sigFileName then
+ res |> addFileName file, sigs |> Set.remove sigFileName
+ else
+ lastFile <- file.FileName
+ res |> addFileNameAndVersion file, sigs)
+ |> fst,
+ lastFile
+
+ let findOutputFileName options =
+ options
+ |> Seq.tryFind (fun (x: string) -> x.StartsWith("-o:"))
+ |> Option.map (fun x -> x.Substring(3))
+
+/// A snapshot of an F# source file.
+[]
+type FSharpFileSnapshot(FileName: string, Version: string, GetSource: unit -> Task) =
+
+ static member Create(fileName: string, version: string, getSource: unit -> Task) =
+ FSharpFileSnapshot(fileName, version, getSource)
+
+ static member CreateFromFileSystem(fileName: string) =
+ FSharpFileSnapshot(
+ fileName,
+ FileSystem.GetLastWriteTimeShim(fileName).Ticks.ToString(),
+ fun () ->
+ FileSystem.OpenFileForReadShim(fileName).ReadAllText()
+ |> SourceTextNew.ofString
+ |> Task.FromResult
+ )
+
+ member public _.FileName = FileName
+ member _.Version = Version
+ member _.GetSource() = GetSource()
+
+ member val IsSignatureFile = FileName |> isSignatureFile
+
+ member _.GetFileName() = FileName
+
+ override this.Equals(o) =
+ match o with
+ | :? FSharpFileSnapshot as o -> o.FileName = this.FileName && o.Version = this.Version
+ | _ -> false
+
+ override this.GetHashCode() =
+ this.FileName.GetHashCode() + this.Version.GetHashCode()
+
+ interface IFileSnapshot with
+ member this.FileName = this.FileName
+ member this.Version = this.Version |> System.Text.Encoding.UTF8.GetBytes
+ member this.IsSignatureFile = this.IsSignatureFile
+
+/// A source file snapshot with loaded source text.
+type internal FSharpFileSnapshotWithSource
+ (FileName: string, SourceHash: ImmutableArray, Source: ISourceText, IsLastCompiland: bool, IsExe: bool) =
+
+ let version = lazy (SourceHash.ToBuilder().ToArray())
+ let stringVersion = lazy (version.Value |> BitConverter.ToString)
+
+ member val Version = version.Value
+ member val StringVersion = stringVersion.Value
+ member val IsSignatureFile = FileName |> isSignatureFile
+
+ member _.FileName = FileName
+ member _.Source = Source
+ member _.IsLastCompiland = IsLastCompiland
+ member _.IsExe = IsExe
+
+ interface IFileSnapshot with
+ member this.FileName = this.FileName
+ member this.Version = this.Version
+ member this.IsSignatureFile = this.IsSignatureFile
+
+/// A source file snapshot with parsed syntax tree
+type internal FSharpParsedFile
+ (
+ FileName: string,
+ SyntaxTreeHash: byte array,
+ SourceText: ISourceText,
+ ParsedInput: ParsedInput,
+ ParseErrors: (PhasedDiagnostic * FSharpDiagnosticSeverity)[]
+ ) =
+
+ member _.FileName = FileName
+ member _.SourceText = SourceText
+ member _.ParsedInput = ParsedInput
+ member _.ParseErrors = ParseErrors
+
+ member val IsSignatureFile = FileName |> isSignatureFile
+
+ interface IFileSnapshot with
+ member this.FileName = this.FileName
+ member this.Version = SyntaxTreeHash
+ member this.IsSignatureFile = this.IsSignatureFile
+
+/// An on-disk reference needed for project compilation.
+[]
+type ReferenceOnDisk =
+ { Path: string; LastModified: DateTime }
+
+/// A snapshot of an F# project. The source file type can differ based on which stage of compilation the snapshot is used for.
+type internal ProjectSnapshotBase<'T when 'T :> IFileSnapshot>(projectCore: ProjectCore, sourceFiles: 'T list) =
+
+ let noFileVersionsHash =
+ lazy
+ (projectCore.Version
+ |> Md5Hasher.addStrings (sourceFiles |> Seq.map (fun x -> x.FileName)))
+
+ let noFileVersionsKey =
+ lazy
+ ({ new ICacheKey<_, _> with
+ member _.GetLabel() = projectCore.Label
+ member _.GetKey() = projectCore.Identifier
+
+ member _.GetVersion() =
+ noFileVersionsHash.Value |> Md5Hasher.toString
+
+ })
+
+ let fullHash =
+ lazy
+ (projectCore.Version
+ |> Md5Hasher.addStrings (
+ sourceFiles
+ |> Seq.collect (fun x ->
+ seq {
+ x.FileName
+ x.Version |> Md5Hasher.toString
+ })
+ ))
+
+ let fullKey =
+ lazy
+ ({ new ICacheKey<_, _> with
+ member _.GetLabel() = projectCore.Label
+ member _.GetKey() = projectCore.Identifier
+ member _.GetVersion() = fullHash.Value |> Md5Hasher.toString
+ })
+
+ let addHash (file: 'T) hash =
+ hash |> Md5Hasher.addString file.FileName |> Md5Hasher.addBytes file.Version
+
+ let signatureHash =
+ lazy (signatureHash projectCore.Version (sourceFiles |> Seq.map (fun x -> x :> IFileSnapshot)))
+
+ let signatureKey =
+ lazy (projectCore.CacheKeyWith("Signature", signatureHash.Value |> fst |> Md5Hasher.toString))
+
+ let lastFileHash =
+ lazy
+ (let lastFile = sourceFiles |> List.last
+ let sigHash, f = signatureHash.Value
+
+ (if f = lastFile.FileName then
+ sigHash
+ else
+ sigHash |> Md5Hasher.addBytes lastFile.Version),
+ lastFile)
+
+ let lastFileKey =
+ lazy
+ (let hash, f = lastFileHash.Value
+
+ { new ICacheKey<_, _> with
+ member _.GetLabel() = $"{f.FileName} ({projectCore.Label})"
+ member _.GetKey() = f.FileName, projectCore.Identifier
+ member _.GetVersion() = hash |> Md5Hasher.toString
+ })
+
+ let sourceFileNames = lazy (sourceFiles |> List.map (fun x -> x.FileName))
+
+ member _.ProjectFileName = projectCore.ProjectFileName
+ member _.ProjectId = projectCore.ProjectId
+ member _.Identifier = projectCore.Identifier
+ member _.ReferencesOnDisk = projectCore.ReferencesOnDisk
+ member _.OtherOptions = projectCore.OtherOptions
+ member _.ReferencedProjects = projectCore.ReferencedProjects
+
+ member _.IsIncompleteTypeCheckEnvironment =
+ projectCore.IsIncompleteTypeCheckEnvironment
+
+ member _.UseScriptResolutionRules = projectCore.UseScriptResolutionRules
+ member _.LoadTime = projectCore.LoadTime
+ member _.UnresolvedReferences = projectCore.UnresolvedReferences
+ member _.OriginalLoadReferences = projectCore.OriginalLoadReferences
+ member _.Stamp = projectCore.Stamp
+ member _.CommandLineOptions = projectCore.CommandLineOptions
+ member _.ProjectDirectory = projectCore.ProjectDirectory
+
+ member _.OutputFileName = projectCore.OutputFileName
+
+ member _.ProjectCore = projectCore
+
+ member _.SourceFiles = sourceFiles
+
+ member _.SourceFileNames = sourceFileNames.Value
+
+ member _.Label = projectCore.Label
+
+ member _.IndexOf fileName =
+ sourceFiles
+ |> List.tryFindIndex (fun x -> x.FileName = fileName)
+ |> Option.defaultWith (fun () -> failwith (sprintf "Unable to find file %s in project %s" fileName projectCore.ProjectFileName))
+
+ member private _.With(sourceFiles: 'T list) =
+ ProjectSnapshotBase(projectCore, sourceFiles)
+
+ /// Create a new snapshot with given source files replacing files in this snapshot with the same name. Other files remain unchanged.
+ member this.Replace(changedSourceFiles: 'T list) =
+ // TODO: validate if changed files are not present in the original list?
+
+ let sourceFiles =
+ sourceFiles
+ |> List.map (fun x ->
+ match changedSourceFiles |> List.tryFind (fun y -> y.FileName = x.FileName) with
+ | Some y -> y
+ | None -> x)
+
+ this.With sourceFiles
+
+ /// Create a new snapshot with source files only up to the given index (inclusive)
+ member this.UpTo fileIndex = this.With sourceFiles[..fileIndex]
+
+ /// Create a new snapshot with source files only up to the given file name (inclusive)
+ member this.UpTo fileName = this.UpTo(this.IndexOf fileName)
+
+ /// Create a new snapshot with only source files at the given indexes
+ member this.OnlyWith fileIndexes =
+ this.With(
+ fileIndexes
+ |> Set.toList
+ |> List.sort
+ |> List.choose (fun x -> sourceFiles |> List.tryItem x)
+ )
+
+ override this.ToString() =
+ Path.GetFileNameWithoutExtension this.ProjectFileName
+ |> sprintf "FSharpProjectSnapshot(%s)"
+
+ /// The newest last modified time of any file in this snapshot including the project file
+ member _.GetLastModifiedTimeOnDisk() =
+ seq {
+ projectCore.ProjectFileName
+
+ yield!
+ sourceFiles
+ |> Seq.filter (fun x -> not (x.FileName.EndsWith(".AssemblyInfo.fs"))) // TODO: is this safe? any better way of doing this?
+ |> Seq.filter (fun x -> not (x.FileName.EndsWith(".AssemblyAttributes.fs")))
+ |> Seq.map (fun x -> x.FileName)
+ }
+ |> Seq.map FileSystem.GetLastWriteTimeShim
+ |> Seq.max
+
+ member _.FullVersion = fullHash.Value
+ member _.SignatureVersion = signatureHash.Value |> fst
+ member _.LastFileVersion = lastFileHash.Value |> fst
+
+ /// Version for parsing - doesn't include any references because they don't affect parsing (...right?)
+ member _.ParsingVersion = projectCore.VersionForParsing |> Md5Hasher.toString
+
+ /// A key for this snapshot but without file versions. So it will be the same across any in-file changes.
+ member _.NoFileVersionsKey = noFileVersionsKey.Value
+
+ /// A full key for this snapshot, any change will cause this to change.
+ member _.FullKey = fullKey.Value
+
+ /// A key including the public surface or signature for this snapshot
+ member _.SignatureKey = signatureKey.Value
+
+ /// A key including the public surface or signature for this snapshot and the last file (even if it's not a signature file)
+ member _.LastFileKey = lastFileKey.Value
+
+ //TODO: cache it here?
+ member this.FileKey(fileName: string) = this.UpTo(fileName).LastFileKey
+ member this.FileKey(index: FileIndex) = this.UpTo(index).LastFileKey
+
+/// Project snapshot with filenames and versions given as initial input
+and internal ProjectSnapshot = ProjectSnapshotBase
+
+/// Project snapshot with file sources loaded
+and internal ProjectSnapshotWithSources = ProjectSnapshotBase
+
+/// All required information for compiling a project except the source files. It's kept separate so it can be reused
+/// for different stages of a project snapshot and also between changes to the source files.
+and internal ProjectCore
+ (
+ ProjectFileName: string,
+ ProjectId: string option,
+ ReferencesOnDisk: ReferenceOnDisk list,
+ OtherOptions: string list,
+ ReferencedProjects: FSharpReferencedProjectSnapshot list,
+ IsIncompleteTypeCheckEnvironment: bool,
+ UseScriptResolutionRules: bool,
+ LoadTime: DateTime,
+ UnresolvedReferences: FSharpUnresolvedReferencesSet option,
+ OriginalLoadReferences: (range * string * string) list,
+ Stamp: int64 option
+ ) as self =
+
+ let hashForParsing =
+ lazy
+ (Md5Hasher.empty
+ |> Md5Hasher.addString ProjectFileName
+ |> Md5Hasher.addStrings OtherOptions
+ |> Md5Hasher.addBool IsIncompleteTypeCheckEnvironment
+ |> Md5Hasher.addBool UseScriptResolutionRules)
+
+ let fullHash =
+ lazy
+ (hashForParsing.Value
+ |> Md5Hasher.addStrings (ReferencesOnDisk |> Seq.map (fun r -> r.Path))
+ |> Md5Hasher.addDateTimes (ReferencesOnDisk |> Seq.map (fun r -> r.LastModified))
+ |> Md5Hasher.addBytes' (
+ ReferencedProjects
+ |> Seq.map (fun (FSharpReference(_name, p)) -> p.ProjectSnapshot.SignatureVersion)
+ ))
+
+ let fullHashString = lazy (fullHash.Value |> Md5Hasher.toString)
+
+ let commandLineOptions =
+ lazy
+ (seq {
+ for r in ReferencesOnDisk do
+ $"-r:{r.Path}"
+
+ yield! OtherOptions
+ }
+ |> Seq.toList)
+
+ let outputFileName = lazy (OtherOptions |> findOutputFileName)
+
+ let key = lazy (ProjectFileName, outputFileName.Value |> Option.defaultValue "")
+
+ let cacheKey =
+ lazy
+ ({ new ICacheKey<_, _> with
+ member _.GetLabel() = self.Label
+ member _.GetKey() = self.Identifier
+ member _.GetVersion() = fullHashString.Value
+ })
+
+ member val ProjectDirectory = Path.GetDirectoryName(ProjectFileName)
+ member _.OutputFileName = outputFileName.Value
+ member _.Identifier: ProjectIdentifier = key.Value
+ member _.Version = fullHash.Value
+ member _.Label = ProjectFileName |> shortPath
+ member _.VersionForParsing = hashForParsing.Value
+
+ member _.CommandLineOptions = commandLineOptions.Value
+
+ member _.ProjectFileName = ProjectFileName
+ member _.ProjectId = ProjectId
+ member _.ReferencesOnDisk = ReferencesOnDisk
+ member _.OtherOptions = OtherOptions
+ member _.ReferencedProjects = ReferencedProjects
+ member _.IsIncompleteTypeCheckEnvironment = IsIncompleteTypeCheckEnvironment
+ member _.UseScriptResolutionRules = UseScriptResolutionRules
+ member _.LoadTime = LoadTime
+ member _.UnresolvedReferences = UnresolvedReferences
+ member _.OriginalLoadReferences = OriginalLoadReferences
+ member _.Stamp = Stamp
+
+ member _.CacheKeyWith(label, version) =
+ { new ICacheKey<_, _> with
+ member _.GetLabel() = $"{label} ({self.Label})"
+ member _.GetKey() = self.Identifier
+ member _.GetVersion() = fullHashString.Value, version
+ }
+
+ member _.CacheKeyWith(label, key, version) =
+ { new ICacheKey<_, _> with
+ member _.GetLabel() = $"{label} ({self.Label})"
+ member _.GetKey() = key, self.Identifier
+ member _.GetVersion() = fullHashString.Value, version
+ }
+
+ member _.CacheKey = cacheKey.Value
+
+and [] FSharpReferencedProjectSnapshot =
+ | FSharpReference of projectOutputFile: string * options: FSharpProjectSnapshot
+ //| PEReference of projectOutputFile: string * getStamp: (unit -> DateTime) * delayedReader: DelayedILModuleReader
+ //| ILModuleReference of
+ // projectOutputFile: string *
+ // getStamp: (unit -> DateTime) *
+ // getReader: (unit -> ILModuleReader)
+
+ ///
+ /// The fully qualified path to the output of the referenced project. This should be the same value as the -r
+ /// reference in the project options for this referenced project.
+ ///
+ member this.OutputFile =
+ match this with
+ | FSharpReference(projectOutputFile, _) -> projectOutputFile
+
+ ///
+ /// Creates a reference for an F# project. The physical data for it is stored/cached inside of the compiler service.
+ ///
+ /// The fully qualified path to the output of the referenced project. This should be the same value as the -r reference in the project options for this referenced project.
+ /// The project snapshot for this F# project
+ static member CreateFSharp(projectOutputFile, snapshot: FSharpProjectSnapshot) =
+ FSharpReference(projectOutputFile, snapshot)
+
+ override this.Equals(o) =
+ match o with
+ | :? FSharpReferencedProjectSnapshot as o ->
+ match this, o with
+ | FSharpReference(projectOutputFile1, options1), FSharpReference(projectOutputFile2, options2) ->
+ projectOutputFile1 = projectOutputFile2 && options1 = options2
+
+ | _ -> false
+
+ override this.GetHashCode() = this.OutputFile.GetHashCode()
+
+/// An identifier of an F# project. This serves to identify the same project as it changes over time and enables us to clear obsolete data from caches.
+and [] FSharpProjectIdentifier =
+ | FSharpProjectIdentifier of projectFileName: string * outputFileName: string
+
+/// A snapshot of an F# project. This type contains all the necessary information for type checking a project.
+and [] FSharpProjectSnapshot internal (projectSnapshot) =
+
+ member internal _.ProjectSnapshot: ProjectSnapshot = projectSnapshot
+
+ /// Create a new snapshot with given source files replacing files in this snapshot with the same name. Other files remain unchanged.
+ member _.Replace(changedSourceFiles: FSharpFileSnapshot list) =
+ projectSnapshot.Replace(changedSourceFiles) |> FSharpProjectSnapshot
+
+ member _.Label = projectSnapshot.Label
+ member _.Identifier = FSharpProjectIdentifier projectSnapshot.ProjectCore.Identifier
+
+ static member Create
+ (
+ projectFileName: string,
+ projectId: string option,
+ sourceFiles: FSharpFileSnapshot list,
+ referencesOnDisk: ReferenceOnDisk list,
+ otherOptions: string list,
+ referencedProjects: FSharpReferencedProjectSnapshot list,
+ isIncompleteTypeCheckEnvironment: bool,
+ useScriptResolutionRules: bool,
+ loadTime: DateTime,
+ unresolvedReferences: FSharpUnresolvedReferencesSet option,
+ originalLoadReferences: (range * string * string) list,
+ stamp: int64 option
+ ) =
+
+ let projectCore =
+ ProjectCore(
+ projectFileName,
+ projectId,
+ referencesOnDisk,
+ otherOptions,
+ referencedProjects,
+ isIncompleteTypeCheckEnvironment,
+ useScriptResolutionRules,
+ loadTime,
+ unresolvedReferences,
+ originalLoadReferences,
+ stamp
+ )
+
+ ProjectSnapshotBase(projectCore, sourceFiles) |> FSharpProjectSnapshot
+
+ static member FromOptions(options: FSharpProjectOptions, getFileSnapshot, ?snapshotAccumulator) =
+ let snapshotAccumulator = defaultArg snapshotAccumulator (Dictionary())
+
+ async {
+
+ // TODO: check if options is a good key here
+ if not (snapshotAccumulator.ContainsKey options) then
+
+ let! sourceFiles = options.SourceFiles |> Seq.map (getFileSnapshot options) |> Async.Parallel
+
+ let! referencedProjects =
+ options.ReferencedProjects
+ |> Seq.choose (function
+ | FSharpReferencedProject.FSharpReference(outputName, options) ->
+ Some(
+ async {
+ let! snapshot = FSharpProjectSnapshot.FromOptions(options, getFileSnapshot, snapshotAccumulator)
+
+ return FSharpReferencedProjectSnapshot.FSharpReference(outputName, snapshot)
+ }
+ )
+ // TODO: other types
+ | _ -> None)
+ |> Async.Sequential
+
+ let referencesOnDisk, otherOptions =
+ options.OtherOptions
+ |> Array.partition (fun x -> x.StartsWith("-r:"))
+ |> map1Of2 (
+ Array.map (fun x ->
+ let path = x.Substring(3)
+
+ {
+ Path = path
+ LastModified = FileSystem.GetLastWriteTimeShim(path)
+ })
+ )
+
+ let snapshot =
+ FSharpProjectSnapshot.Create(
+ projectFileName = options.ProjectFileName,
+ projectId = options.ProjectId,
+ sourceFiles = (sourceFiles |> List.ofArray),
+ referencesOnDisk = (referencesOnDisk |> List.ofArray),
+ otherOptions = (otherOptions |> List.ofArray),
+ referencedProjects = (referencedProjects |> List.ofArray),
+ isIncompleteTypeCheckEnvironment = options.IsIncompleteTypeCheckEnvironment,
+ useScriptResolutionRules = options.UseScriptResolutionRules,
+ loadTime = options.LoadTime,
+ unresolvedReferences = options.UnresolvedReferences,
+ originalLoadReferences = options.OriginalLoadReferences,
+ stamp = options.Stamp
+ )
+
+ snapshotAccumulator.Add(options, snapshot)
+
+ return snapshotAccumulator[options]
+ }
+
+ static member internal GetFileSnapshotFromDisk _ fileName =
+ FSharpFileSnapshot.CreateFromFileSystem fileName |> async.Return
+
+ static member FromOptions(options: FSharpProjectOptions) =
+ FSharpProjectSnapshot.FromOptions(options, FSharpProjectSnapshot.GetFileSnapshotFromDisk)
+
+ static member FromOptions(options: FSharpProjectOptions, fileName: string, fileVersion: int, sourceText: ISourceText) =
+
+ let getFileSnapshot _ fName =
+ if fName = fileName then
+ FSharpFileSnapshot.Create(
+ fileName,
+ $"{fileVersion}{sourceText.GetHashCode().ToString()}",
+ fun () -> Task.FromResult(SourceTextNew.ofISourceText sourceText)
+ )
+ else
+ FSharpFileSnapshot.CreateFromFileSystem fName
+ |> async.Return
+
+ FSharpProjectSnapshot.FromOptions(options, getFileSnapshot)
+
+let rec internal snapshotToOptions (projectSnapshot: ProjectSnapshotBase<_>) =
+ {
+ ProjectFileName = projectSnapshot.ProjectFileName
+ ProjectId = projectSnapshot.ProjectId
+ SourceFiles = projectSnapshot.SourceFiles |> Seq.map (fun x -> x.FileName) |> Seq.toArray
+ OtherOptions = projectSnapshot.CommandLineOptions |> List.toArray
+ ReferencedProjects =
+ projectSnapshot.ReferencedProjects
+ |> Seq.map (function
+ | FSharpReference(name, opts) -> FSharpReferencedProject.FSharpReference(name, opts.ProjectSnapshot |> snapshotToOptions))
+ |> Seq.toArray
+ IsIncompleteTypeCheckEnvironment = projectSnapshot.IsIncompleteTypeCheckEnvironment
+ UseScriptResolutionRules = projectSnapshot.UseScriptResolutionRules
+ LoadTime = projectSnapshot.LoadTime
+ UnresolvedReferences = projectSnapshot.UnresolvedReferences
+ OriginalLoadReferences = projectSnapshot.OriginalLoadReferences
+ Stamp = projectSnapshot.Stamp
+ }
+
+[]
+type internal Extensions =
+
+ []
+ static member ToOptions(this: ProjectSnapshot) = this |> snapshotToOptions
+
+ []
+ static member ToOptions(this: FSharpProjectSnapshot) =
+ this.ProjectSnapshot |> snapshotToOptions
+
+ []
+ static member GetProjectIdentifier(this: FSharpProjectOptions) : ProjectIdentifier =
+ this.ProjectFileName, this.OtherOptions |> findOutputFileName |> Option.defaultValue ""
diff --git a/src/Compiler/Service/IncrementalBuild.fs b/src/Compiler/Service/IncrementalBuild.fs
index f6289a283ac..f59a1e9b6a5 100644
--- a/src/Compiler/Service/IncrementalBuild.fs
+++ b/src/Compiler/Service/IncrementalBuild.fs
@@ -485,10 +485,19 @@ type BoundModel private (
syntaxTreeOpt
)
-
/// Global service state
-type FrameworkImportsCacheKey = FrameworkImportsCacheKey of resolvedpath: string list * assemblyName: string * targetFrameworkDirectories: string list * fsharpBinaries: string * langVersion: decimal
+type FrameworkImportsCacheKey =
+ | FrameworkImportsCacheKey of resolvedpath: string list * assemblyName: string * targetFrameworkDirectories: string list * fsharpBinaries: string * langVersion: decimal
+
+ interface ICacheKey with
+ member this.GetKey() =
+ this |> function FrameworkImportsCacheKey(assemblyName=a) -> a
+ member this.GetLabel() =
+ this |> function FrameworkImportsCacheKey(assemblyName=a) -> a
+
+ member this.GetVersion() = this
+
/// Represents a cache of 'framework' references that can be shared between multiple incremental builds
type FrameworkImportsCache(size) =
@@ -593,6 +602,7 @@ module Utilities =
/// Constructs the build data (IRawFSharpAssemblyData) representing the assembly when used
/// as a cross-assembly reference. Note the assembly has not been generated on disk, so this is
/// a virtualized view of the assembly contents as computed by background checking.
+[]
type RawFSharpAssemblyDataBackedByLanguageService (tcConfig, tcGlobals, generatedCcu: CcuThunk, outfile, topAttrs, assemblyName, ilAssemRef) =
let exportRemapping = MakeExportRemapping generatedCcu generatedCcu.Contents
diff --git a/src/Compiler/Service/IncrementalBuild.fsi b/src/Compiler/Service/IncrementalBuild.fsi
index b4e60d403f0..0dedfb02948 100644
--- a/src/Compiler/Service/IncrementalBuild.fsi
+++ b/src/Compiler/Service/IncrementalBuild.fsi
@@ -22,6 +22,17 @@ open FSharp.Compiler.TcGlobals
open FSharp.Compiler.Text
open FSharp.Compiler.TypedTree
open FSharp.Compiler.BuildGraph
+open Internal.Utilities.Collections
+
+type internal FrameworkImportsCacheKey =
+ | FrameworkImportsCacheKey of
+ resolvedpath: string list *
+ assemblyName: string *
+ targetFrameworkDirectories: string list *
+ fsharpBinaries: string *
+ langVersion: decimal
+
+ interface ICacheKey
/// Lookup the global static cache for building the FrameworkTcImports
type internal FrameworkImportsCache =
@@ -132,6 +143,20 @@ type internal PartialCheckResults =
member TimeStamp: DateTime
+[]
+type internal RawFSharpAssemblyDataBackedByLanguageService =
+ new:
+ tcConfig: TcConfig *
+ tcGlobals: TcGlobals *
+ generatedCcu: CcuThunk *
+ outfile: string *
+ topAttrs: TopAttribs *
+ assemblyName: string *
+ ilAssemRef: FSharp.Compiler.AbstractIL.IL.ILAssemblyRef ->
+ RawFSharpAssemblyDataBackedByLanguageService
+
+ interface IRawFSharpAssemblyData
+
/// Manages an incremental build graph for the build of an F# project
[]
type internal IncrementalBuilder =
diff --git a/src/Compiler/Service/ServiceAnalysis.fs b/src/Compiler/Service/ServiceAnalysis.fs
index 89569a802c8..29df63b1194 100644
--- a/src/Compiler/Service/ServiceAnalysis.fs
+++ b/src/Compiler/Service/ServiceAnalysis.fs
@@ -8,7 +8,6 @@ open System.Runtime.CompilerServices
open Internal.Utilities.Library
open FSharp.Compiler.CodeAnalysis
open FSharp.Compiler.Symbols
-open FSharp.Compiler.Syntax
open FSharp.Compiler.Syntax.PrettyNaming
open FSharp.Compiler.Text
open FSharp.Compiler.Text.Range
@@ -463,1210 +462,3 @@ module UnusedDeclarations =
let unusedRanges = getUnusedDeclarationRanges allSymbolUsesInFile isScriptFile
return unusedRanges
}
-
-module UnnecessaryParentheses =
- open System
-
- let (|Ident|) (ident: Ident) = ident.idText
-
- /// Represents a symbolic infix operator with the precedence of *, /, or %.
- /// All instances of this type are considered equal.
- []
- type MulDivMod =
- | Mul
- | Div
- | Mod
-
- member _.CompareTo(_other: MulDivMod) = 0
- override this.Equals obj = this.CompareTo(unbox obj) = 0
- override _.GetHashCode() = 0
-
- interface IComparable with
- member this.CompareTo obj = this.CompareTo(unbox obj)
-
- /// Represents a symbolic infix operator with the precedence of + or -.
- /// All instances of this type are considered equal.
- []
- type AddSub =
- | Add
- | Sub
-
- member _.CompareTo(_other: AddSub) = 0
- override this.Equals obj = this.CompareTo(unbox obj) = 0
- override _.GetHashCode() = 0
-
- interface IComparable with
- member this.CompareTo obj = this.CompareTo(unbox obj)
-
- /// Holds a symbolic operator's original notation.
- /// Equality is based on the contents of the string.
- /// Comparison always returns 0.
- []
- type OriginalNotation =
- | OriginalNotation of string
-
- member _.CompareTo(_other: OriginalNotation) = 0
-
- override this.Equals obj =
- match this, obj with
- | OriginalNotation this, (:? OriginalNotation as OriginalNotation other) -> String.Equals(this, other, StringComparison.Ordinal)
- | _ -> false
-
- override this.GetHashCode() =
- match this with
- | OriginalNotation notation -> notation.GetHashCode()
-
- interface IComparable with
- member this.CompareTo obj = this.CompareTo(unbox obj)
-
- /// Represents an expression's precedence.
- /// Comparison is based only on the precedence case.
- /// Equality considers the embedded original notation, if any.
- ///
- /// For example:
- ///
- /// compare (AddSub (Add, OriginalNotation "+")) (AddSub (Add, OriginalNotation "++")) = 0
- ///
- /// but
- ///
- /// AddSub (Add, OriginalNotation "+") <> AddSub (Add, OriginalNotation "++")
- type Precedence =
- /// yield, yield!, return, return!
- | Low
-
- /// <-
- | Set
-
- /// :=
- | ColonEquals
-
- /// ,
- | Comma
-
- /// or, ||
- ///
- /// Refers to the exact operators or and ||.
- /// Instances with leading dots or question marks or trailing characters are parsed as Bar instead.
- | BarBar of OriginalNotation
-
- /// &, &&
- ///
- /// Refers to the exact operators & and &&.
- /// Instances with leading dots or question marks or trailing characters are parsed as Amp instead.
- | AmpAmp of OriginalNotation
-
- /// :>, :?>
- | UpcastDowncast
-
- /// =…, |…, &…, $…, >…, <…, !=…
- | Relational of OriginalNotation
-
- /// ^…, @…
- | HatAt
-
- /// ::
- | Cons
-
- /// :?
- | TypeTest
-
- /// +…, -…
- | AddSub of AddSub * OriginalNotation
-
- /// *…, /…, %…
- | MulDivMod of MulDivMod * OriginalNotation
-
- /// **…
- | Exp
-
- /// - x
- | UnaryPrefix
-
- /// f x
- | Apply
-
- /// -x, !… x, ~~… x
- | High
-
- // x.y
- | Dot
-
- /// Associativity/association.
- type Assoc =
- /// Non-associative or no association.
- | Non
-
- /// Left-associative or left-hand association.
- | Left
-
- /// Right-associative or right-hand association.
- | Right
-
- module Assoc =
- let ofPrecedence precedence =
- match precedence with
- | Low -> Non
- | Set -> Non
- | ColonEquals -> Right
- | Comma -> Non
- | BarBar _ -> Left
- | AmpAmp _ -> Left
- | UpcastDowncast -> Right
- | Relational _ -> Left
- | HatAt -> Right
- | Cons -> Right
- | TypeTest -> Non
- | AddSub _ -> Left
- | MulDivMod _ -> Left
- | Exp -> Right
- | UnaryPrefix -> Left
- | Apply -> Left
- | High -> Left
- | Dot -> Left
-
- /// Matches if the two expressions or patterns refer to the same object.
- []
- let inline (|Is|_|) (inner1: 'a) (inner2: 'a) =
- if obj.ReferenceEquals(inner1, inner2) then
- ValueSome Is
- else
- ValueNone
-
- module SynExpr =
- open FSharp.Compiler.SyntaxTrivia
-
- /// See atomicExprAfterType in pars.fsy.
- []
- let (|AtomicExprAfterType|_|) expr =
- match expr with
- | SynExpr.Paren _
- | SynExpr.Quote _
- | SynExpr.Const _
- | SynExpr.Tuple(isStruct = true)
- | SynExpr.Record _
- | SynExpr.AnonRecd _
- | SynExpr.InterpolatedString _
- | SynExpr.Null _
- | SynExpr.ArrayOrList(isArray = true)
- | SynExpr.ArrayOrListComputed(isArray = true) -> ValueSome AtomicExprAfterType
- | _ -> ValueNone
-
- /// Matches if the given expression represents a high-precedence
- /// function application, e.g.,
- ///
- /// f x
- ///
- /// (+) x y
- []
- let (|HighPrecedenceApp|_|) expr =
- match expr with
- | SynExpr.App(isInfix = false; funcExpr = SynExpr.Ident _)
- | SynExpr.App(isInfix = false; funcExpr = SynExpr.LongIdent _)
- | SynExpr.App(isInfix = false; funcExpr = SynExpr.App(isInfix = false)) -> ValueSome HighPrecedenceApp
- | _ -> ValueNone
-
- module FuncExpr =
- /// Matches when the given funcExpr is a direct application
- /// of a symbolic operator, e.g., -, _not_ (~-).
- []
- let (|SymbolicOperator|_|) funcExpr =
- match funcExpr with
- | SynExpr.LongIdent(longDotId = SynLongIdent(trivia = trivia)) ->
- let rec tryPick =
- function
- | [] -> ValueNone
- | Some(IdentTrivia.OriginalNotation op) :: _ -> ValueSome op
- | _ :: rest -> tryPick rest
-
- tryPick trivia
- | _ -> ValueNone
-
- /// Matches when the given expression is a prefix operator application, e.g.,
- ///
- /// -x
- ///
- /// ~~~x
- []
- let (|PrefixApp|_|) expr : Precedence voption =
- match expr with
- | SynExpr.App(isInfix = false; funcExpr = funcExpr & FuncExpr.SymbolicOperator op; argExpr = argExpr) ->
- if funcExpr.Range.IsAdjacentTo argExpr.Range then
- ValueSome High
- else
- assert (op.Length > 0)
-
- match op[0] with
- | '!'
- | '~' -> ValueSome High
- | _ -> ValueSome UnaryPrefix
-
- | SynExpr.AddressOf(expr = expr; opRange = opRange) ->
- if opRange.IsAdjacentTo expr.Range then
- ValueSome High
- else
- ValueSome UnaryPrefix
-
- | _ -> ValueNone
-
- /// Tries to parse the given original notation as a symbolic infix operator.
- []
- let (|SymbolPrec|_|) (originalNotation: string) =
- // Trim any leading dots or question marks from the given symbolic operator.
- // Leading dots or question marks have no effect on operator precedence or associativity
- // with the exception of &, &&, and ||.
- let ignoredLeadingChars = ".?".AsSpan()
- let trimmed = originalNotation.AsSpan().TrimStart ignoredLeadingChars
- assert (trimmed.Length > 0)
-
- match trimmed[0], originalNotation with
- | _, ":=" -> ValueSome ColonEquals
- | _, ("||" | "or") -> ValueSome(BarBar(OriginalNotation originalNotation))
- | _, ("&" | "&&") -> ValueSome(AmpAmp(OriginalNotation originalNotation))
- | '|', _
- | '&', _
- | '<', _
- | '>', _
- | '=', _
- | '$', _ -> ValueSome(Relational(OriginalNotation originalNotation))
- | '!', _ when trimmed.Length > 1 && trimmed[1] = '=' -> ValueSome(Relational(OriginalNotation originalNotation))
- | '^', _
- | '@', _ -> ValueSome HatAt
- | _, "::" -> ValueSome Cons
- | '+', _ -> ValueSome(AddSub(Add, OriginalNotation originalNotation))
- | '-', _ -> ValueSome(AddSub(Sub, OriginalNotation originalNotation))
- | '/', _ -> ValueSome(MulDivMod(Div, OriginalNotation originalNotation))
- | '%', _ -> ValueSome(MulDivMod(Mod, OriginalNotation originalNotation))
- | '*', _ when trimmed.Length > 1 && trimmed[1] = '*' -> ValueSome Exp
- | '*', _ -> ValueSome(MulDivMod(Mul, OriginalNotation originalNotation))
- | _ -> ValueNone
-
- []
- let (|Contains|_|) (c: char) (s: string) =
- if s.IndexOf c >= 0 then ValueSome Contains else ValueNone
-
- /// Any expressions in which the removal of parens would
- /// lead to something like the following that would be
- /// confused by the parser with a type parameter application:
- ///
- /// xz
- ///
- /// xz
- []
- let rec (|ConfusableWithTypeApp|_|) synExpr =
- match synExpr with
- | SynExpr.Paren(expr = ConfusableWithTypeApp)
- | SynExpr.App(funcExpr = ConfusableWithTypeApp)
- | SynExpr.App(isInfix = true; funcExpr = FuncExpr.SymbolicOperator(Contains '>'); argExpr = ConfusableWithTypeApp) ->
- ValueSome ConfusableWithTypeApp
- | SynExpr.App(isInfix = true; funcExpr = funcExpr & FuncExpr.SymbolicOperator(Contains '<'); argExpr = argExpr) when
- argExpr.Range.IsAdjacentTo funcExpr.Range
- ->
- ValueSome ConfusableWithTypeApp
- | SynExpr.Tuple(exprs = exprs) ->
- let rec anyButLast =
- function
- | _ :: []
- | [] -> ValueNone
- | ConfusableWithTypeApp :: _ -> ValueSome ConfusableWithTypeApp
- | _ :: tail -> anyButLast tail
-
- anyButLast exprs
- | _ -> ValueNone
-
- /// Matches when the expression represents the infix application of a symbolic operator.
- ///
- /// (x λ y) ρ z
- ///
- /// x λ (y ρ z)
- []
- let (|InfixApp|_|) synExpr : struct (Precedence * Assoc) voption =
- match synExpr with
- | SynExpr.App(funcExpr = SynExpr.App(isInfix = true; funcExpr = FuncExpr.SymbolicOperator(SymbolPrec prec))) ->
- ValueSome(prec, Right)
- | SynExpr.App(isInfix = true; funcExpr = FuncExpr.SymbolicOperator(SymbolPrec prec)) -> ValueSome(prec, Left)
- | SynExpr.Upcast _
- | SynExpr.Downcast _ -> ValueSome(UpcastDowncast, Left)
- | SynExpr.TypeTest _ -> ValueSome(TypeTest, Left)
- | _ -> ValueNone
-
- /// Returns the given expression's precedence and the side of the inner expression,
- /// if applicable.
- []
- let (|OuterBinaryExpr|_|) inner outer : struct (Precedence * Assoc) voption =
- match outer with
- | SynExpr.YieldOrReturn _
- | SynExpr.YieldOrReturnFrom _ -> ValueSome(Low, Right)
- | SynExpr.Tuple(exprs = SynExpr.Paren(expr = Is inner) :: _) -> ValueSome(Comma, Left)
- | SynExpr.Tuple _ -> ValueSome(Comma, Right)
- | InfixApp(Cons, side) -> ValueSome(Cons, side)
- | SynExpr.Assert _
- | SynExpr.Lazy _
- | SynExpr.InferredUpcast _
- | SynExpr.InferredDowncast _ -> ValueSome(Apply, Non)
- | PrefixApp prec -> ValueSome(prec, Non)
- | InfixApp(prec, side) -> ValueSome(prec, side)
- | SynExpr.App(argExpr = SynExpr.ComputationExpr _) -> ValueSome(UnaryPrefix, Left)
- | SynExpr.App(funcExpr = SynExpr.Paren(expr = SynExpr.App _)) -> ValueSome(Apply, Left)
- | SynExpr.App _ -> ValueSome(Apply, Non)
- | SynExpr.DotSet(targetExpr = SynExpr.Paren(expr = Is inner)) -> ValueSome(Dot, Left)
- | SynExpr.DotSet(rhsExpr = SynExpr.Paren(expr = Is inner)) -> ValueSome(Set, Right)
- | SynExpr.DotIndexedSet(objectExpr = SynExpr.Paren(expr = Is inner))
- | SynExpr.DotNamedIndexedPropertySet(targetExpr = SynExpr.Paren(expr = Is inner)) -> ValueSome(Dot, Left)
- | SynExpr.DotIndexedSet(valueExpr = SynExpr.Paren(expr = Is inner))
- | SynExpr.DotNamedIndexedPropertySet(rhsExpr = SynExpr.Paren(expr = Is inner)) -> ValueSome(Set, Right)
- | SynExpr.LongIdentSet(expr = SynExpr.Paren(expr = Is inner)) -> ValueSome(Set, Right)
- | SynExpr.Set _ -> ValueSome(Set, Non)
- | SynExpr.DotGet _ -> ValueSome(Dot, Left)
- | SynExpr.DotIndexedGet(objectExpr = SynExpr.Paren(expr = Is inner)) -> ValueSome(Dot, Left)
- | _ -> ValueNone
-
- /// Matches a SynExpr.App nested in a sequence of dot-gets.
- ///
- /// x.M.N().O
- []
- let (|NestedApp|_|) expr =
- let rec loop =
- function
- | SynExpr.DotGet(expr = expr)
- | SynExpr.DotIndexedGet(objectExpr = expr) -> loop expr
- | SynExpr.App _ -> ValueSome NestedApp
- | _ -> ValueNone
-
- loop expr
-
- /// Returns the given expression's precedence, if applicable.
- []
- let (|InnerBinaryExpr|_|) expr : Precedence voption =
- match expr with
- | SynExpr.Tuple(isStruct = false) -> ValueSome Comma
- | SynExpr.DotGet(expr = NestedApp)
- | SynExpr.DotIndexedGet(objectExpr = NestedApp) -> ValueSome Apply
- | SynExpr.DotGet _
- | SynExpr.DotIndexedGet _ -> ValueSome Dot
- | PrefixApp prec -> ValueSome prec
- | InfixApp(prec, _) -> ValueSome prec
- | SynExpr.App _
- | SynExpr.Assert _
- | SynExpr.Lazy _
- | SynExpr.For _
- | SynExpr.ForEach _
- | SynExpr.While _
- | SynExpr.Do _
- | SynExpr.New _
- | SynExpr.InferredUpcast _
- | SynExpr.InferredDowncast _ -> ValueSome Apply
- | SynExpr.DotIndexedSet _
- | SynExpr.DotNamedIndexedPropertySet _
- | SynExpr.DotSet _ -> ValueSome Set
- | _ -> ValueNone
-
- module Dangling =
- /// Returns the first matching nested right-hand target expression, if any.
- let private dangling (target: SynExpr -> SynExpr option) =
- let (|Target|_|) = target
- let (|Last|) = List.last
-
- let rec loop expr =
- match expr with
- | Target expr -> ValueSome expr
- | SynExpr.Tuple(isStruct = false; exprs = Last expr)
- | SynExpr.App(argExpr = expr)
- | SynExpr.IfThenElse(elseExpr = Some expr)
- | SynExpr.IfThenElse(ifExpr = expr)
- | SynExpr.Sequential(expr2 = expr)
- | SynExpr.YieldOrReturn(expr = expr)
- | SynExpr.YieldOrReturnFrom(expr = expr)
- | SynExpr.Set(rhsExpr = expr)
- | SynExpr.DotSet(rhsExpr = expr)
- | SynExpr.DotNamedIndexedPropertySet(rhsExpr = expr)
- | SynExpr.DotIndexedSet(valueExpr = expr)
- | SynExpr.LongIdentSet(expr = expr)
- | SynExpr.LetOrUse(body = expr)
- | SynExpr.Lambda(body = expr)
- | SynExpr.Match(clauses = Last(SynMatchClause(resultExpr = expr)))
- | SynExpr.MatchLambda(matchClauses = Last(SynMatchClause(resultExpr = expr)))
- | SynExpr.MatchBang(clauses = Last(SynMatchClause(resultExpr = expr)))
- | SynExpr.TryWith(withCases = Last(SynMatchClause(resultExpr = expr)))
- | SynExpr.TryFinally(finallyExpr = expr) -> loop expr
- | _ -> ValueNone
-
- loop
-
- /// Matches a dangling if-then construct.
- []
- let (|IfThen|_|) =
- dangling (function
- | SynExpr.IfThenElse _ as expr -> Some expr
- | _ -> None)
-
- /// Matches a dangling sequential expression.
- []
- let (|Sequential|_|) =
- dangling (function
- | SynExpr.Sequential _ as expr -> Some expr
- | _ -> None)
-
- /// Matches a dangling try-with or try-finally construct.
- []
- let (|Try|_|) =
- dangling (function
- | SynExpr.TryWith _
- | SynExpr.TryFinally _ as expr -> Some expr
- | _ -> None)
-
- /// Matches a dangling match-like construct.
- []
- let (|Match|_|) =
- dangling (function
- | SynExpr.Match _
- | SynExpr.MatchBang _
- | SynExpr.MatchLambda _
- | SynExpr.TryWith _
- | SynExpr.Lambda _ as expr -> Some expr
- | _ -> None)
-
- /// Matches a nested dangling construct that could become problematic
- /// if the surrounding parens were removed.
- [