diff --git a/.gitattributes b/.gitattributes
index 6e2f3ce760..22ea220d8f 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -28,5 +28,6 @@ targets.make text eol=lf
*.sh text eol=lf
*.bsl linguist-vendored=true
+*.xlf linguist-generated=true
*.png binary
diff --git a/azure-pipelines.yml b/azure-pipelines.yml
index ed921cab8e..5171df4967 100644
--- a/azure-pipelines.yml
+++ b/azure-pipelines.yml
@@ -223,7 +223,7 @@ stages:
regular:
_experimental_flag: ''
experimental_features:
- _experimental_flag: 1
+ _experimental_flag: ''
steps:
- checkout: self
clean: true
@@ -513,7 +513,7 @@ stages:
regular:
_experimental_flag: ''
experimental_features:
- _experimental_flag: 1
+ _experimental_flag: ''
steps:
- checkout: self
clean: true
diff --git a/src/Compiler/Driver/GraphChecking/TypeCheckingGraphProcessing.fs b/src/Compiler/Driver/GraphChecking/TypeCheckingGraphProcessing.fs
deleted file mode 100644
index 7f184a5888..0000000000
--- a/src/Compiler/Driver/GraphChecking/TypeCheckingGraphProcessing.fs
+++ /dev/null
@@ -1,104 +0,0 @@
-module internal FSharp.Compiler.GraphChecking.TypeCheckingGraphProcessing
-
-open GraphProcessing
-open System.Collections.Generic
-open System.Threading
-
-// TODO Do we need to suppress some error logging if we
-// TODO apply the same partial results multiple times?
-// TODO Maybe we can enable logging only for the final fold
-///
-/// Combine type-checking results of dependencies needed to type-check a 'higher' node in the graph
-///
-/// Initial state
-/// Direct dependencies of a node
-/// Transitive dependencies of a node
-/// A way to fold a single result into existing state
-///
-/// Similar to 'processFileGraph', this function is generic yet specific to the type-checking process.
-///
-let combineResults
- (emptyState: 'State)
- (deps: ProcessedNode<'Item, 'State * Finisher<'State, 'FinalFileResult>>[])
- (transitiveDeps: ProcessedNode<'Item, 'State * Finisher<'State, 'FinalFileResult>>[])
- (folder: 'State -> Finisher<'State, 'FinalFileResult> -> 'State)
- : 'State =
- match deps with
- | [||] -> emptyState
- | _ ->
- // Instead of starting with empty state,
- // reuse state produced by the dependency with the biggest number of transitive dependencies.
- // This is to reduce the number of folds required to achieve the final state.
- let biggestDependency =
- let sizeMetric (node: ProcessedNode<_, _>) = node.Info.TransitiveDeps.Length
- deps |> Array.maxBy sizeMetric
-
- let firstState = biggestDependency.Result |> fst
-
- // Find items not already included in the state.
- // Note: Ordering is not preserved due to reusing results of the biggest child
- // rather than starting with empty state
- let itemsPresent =
- set
- [|
- yield! biggestDependency.Info.TransitiveDeps
- yield biggestDependency.Info.Item
- |]
-
- let resultsToAdd =
- transitiveDeps
- |> Array.filter (fun dep -> itemsPresent.Contains dep.Info.Item = false)
- |> Array.distinctBy (fun dep -> dep.Info.Item)
- |> Array.map (fun dep -> dep.Result |> snd)
-
- // Fold results not already included and produce the final state
- let state = Array.fold folder firstState resultsToAdd
- state
-
-// TODO This function and its parameters are quite specific to type-checking despite using generic types.
-// Perhaps we should make it either more specific and remove type parameters, or more generic.
-///
-/// Process a graph of items.
-/// A version of 'GraphProcessing.processGraph' with a signature slightly specific to type-checking.
-///
-let processTypeCheckingGraph<'Item, 'ChosenItem, 'State, 'FinalFileResult when 'Item: equality and 'Item: comparison>
- (graph: Graph<'Item>)
- (work: 'Item -> 'State -> Finisher<'State, 'FinalFileResult>)
- (folder: 'State -> Finisher<'State, 'FinalFileResult> -> 'FinalFileResult * 'State)
- // Decides whether a result for an item should be included in the final state, and how to map the item if it should.
- (finalStateChooser: 'Item -> 'ChosenItem option)
- (emptyState: 'State)
- (ct: CancellationToken)
- : ('ChosenItem * 'FinalFileResult) list * 'State =
-
- let workWrapper
- (getProcessedNode: 'Item -> ProcessedNode<'Item, 'State * Finisher<'State, 'FinalFileResult>>)
- (node: NodeInfo<'Item>)
- : 'State * Finisher<'State, 'FinalFileResult> =
- let folder x y = folder x y |> snd
- let deps = node.Deps |> Array.except [| node.Item |] |> Array.map getProcessedNode
-
- let transitiveDeps =
- node.TransitiveDeps
- |> Array.except [| node.Item |]
- |> Array.map getProcessedNode
-
- let inputState = combineResults emptyState deps transitiveDeps folder
- let singleRes = work node.Item inputState
- let state = folder inputState singleRes
- state, singleRes
-
- let results = processGraph graph workWrapper ct
-
- let finalFileResults, state: ('ChosenItem * 'FinalFileResult) list * 'State =
- (([], emptyState),
- results
- |> Array.choose (fun (item, res) ->
- match finalStateChooser item with
- | Some item -> Some(item, res)
- | None -> None))
- ||> Array.fold (fun (fileResults, state) (item, (_, itemRes)) ->
- let fileResult, state = folder state itemRes
- (item, fileResult) :: fileResults, state)
-
- finalFileResults, state
diff --git a/src/Compiler/Driver/GraphChecking/TypeCheckingGraphProcessing.fsi b/src/Compiler/Driver/GraphChecking/TypeCheckingGraphProcessing.fsi
deleted file mode 100644
index 5db01dc330..0000000000
--- a/src/Compiler/Driver/GraphChecking/TypeCheckingGraphProcessing.fsi
+++ /dev/null
@@ -1,17 +0,0 @@
-/// Parallel processing of a type-checking file graph.
-module internal FSharp.Compiler.GraphChecking.TypeCheckingGraphProcessing
-
-open System.Threading
-
-///
-/// Process a graph of items.
-/// A version of 'GraphProcessing.processGraph' with a signature slightly specific to type-checking.
-///
-val processTypeCheckingGraph<'Item, 'ChosenItem, 'State, 'FinalFileResult when 'Item: equality and 'Item: comparison> :
- graph: Graph<'Item> ->
- work: ('Item -> 'State -> Finisher<'State, 'FinalFileResult>) ->
- folder: ('State -> Finisher<'State, 'FinalFileResult> -> 'FinalFileResult * 'State) ->
- finalStateChooser: ('Item -> 'ChosenItem option) ->
- emptyState: 'State ->
- ct: CancellationToken ->
- ('ChosenItem * 'FinalFileResult) list * 'State
diff --git a/src/Compiler/Driver/GraphChecking/Types.fs b/src/Compiler/Driver/GraphChecking/Types.fs
index c0e8e0f84b..04359519b9 100644
--- a/src/Compiler/Driver/GraphChecking/Types.fs
+++ b/src/Compiler/Driver/GraphChecking/Types.fs
@@ -167,4 +167,4 @@ type internal FilePairMap(files: FileInProject array) =
member x.IsSignature(index: FileIndex) = Map.containsKey index sigToImpl
/// Callback that returns a previously calculated 'Result and updates 'State accordingly.
-type internal Finisher<'State, 'Result> = delegate of 'State -> 'Result * 'State
+type internal Finisher<'Node, 'State, 'Result> = Finisher of node: 'Node * finisher: ('State -> 'Result * 'State)
diff --git a/src/Compiler/Driver/GraphChecking/Types.fsi b/src/Compiler/Driver/GraphChecking/Types.fsi
index 7d0ba9bbdd..67403d51d9 100644
--- a/src/Compiler/Driver/GraphChecking/Types.fsi
+++ b/src/Compiler/Driver/GraphChecking/Types.fsi
@@ -114,4 +114,4 @@ type internal FilePairMap =
member IsSignature: index: FileIndex -> bool
/// Callback that returns a previously calculated 'Result and updates 'State accordingly.
-type internal Finisher<'State, 'Result> = delegate of 'State -> 'Result * 'State
+type internal Finisher<'Node, 'State, 'Result> = Finisher of node: 'Node * finisher: ('State -> 'Result * 'State)
diff --git a/src/Compiler/Driver/ParseAndCheckInputs.fs b/src/Compiler/Driver/ParseAndCheckInputs.fs
index dfdbdf682d..a731cc2e05 100644
--- a/src/Compiler/Driver/ParseAndCheckInputs.fs
+++ b/src/Compiler/Driver/ParseAndCheckInputs.fs
@@ -1472,11 +1472,10 @@ type NodeToTypeCheck =
/// Even though the actual implementation file was not type-checked.
| ArtificialImplFile of signatureFileIndex: FileIndex
-let folder (state: State) (finisher: Finisher) : FinalFileResult * State = finisher.Invoke(state)
-
/// Typecheck a single file (or interactive entry into F# Interactive)
/// a callback functions that takes a `TcState` and will add the checked result to it.
let CheckOneInputWithCallback
+ (node: NodeToTypeCheck)
((checkForErrors,
tcConfig: TcConfig,
tcImports: TcImports,
@@ -1486,7 +1485,7 @@ let CheckOneInputWithCallback
tcState: TcState,
inp: ParsedInput,
_skipImplIfSigExists: bool): (unit -> bool) * TcConfig * TcImports * TcGlobals * LongIdent option * TcResultsSink * TcState * ParsedInput * bool)
- : Cancellable> =
+ : Cancellable> =
cancellable {
try
CheckSimulateException tcConfig
@@ -1535,25 +1534,29 @@ let CheckOneInputWithCallback
TcOpenModuleOrNamespaceDecl tcSink tcGlobals amap m tcEnv (prefixPath, m)
return
- Finisher(fun tcState ->
- let rootSigs = Zmap.add qualNameOfFile sigFileType tcState.tcsRootSigs
+ Finisher(
+ node,
+ (fun tcState ->
+ let rootSigs = Zmap.add qualNameOfFile sigFileType tcState.tcsRootSigs
- let tcSigEnv =
- AddLocalRootModuleOrNamespace TcResultsSink.NoSink tcGlobals amap m tcState.tcsTcSigEnv sigFileType
+ let tcSigEnv =
+ AddLocalRootModuleOrNamespace TcResultsSink.NoSink tcGlobals amap m tcState.tcsTcSigEnv sigFileType
- // Add the signature to the signature env (unless it had an explicit signature)
- let ccuSigForFile = CombineCcuContentFragments [ sigFileType; tcState.tcsCcuSig ]
+ // Add the signature to the signature env (unless it had an explicit signature)
+ let ccuSigForFile = CombineCcuContentFragments [ sigFileType; tcState.tcsCcuSig ]
- let partialResult = tcEnv, EmptyTopAttrs, None, ccuSigForFile
+ let partialResult = tcEnv, EmptyTopAttrs, None, ccuSigForFile
- let tcState =
- { tcState with
- tcsTcSigEnv = tcSigEnv
- tcsRootSigs = rootSigs
- tcsCreatesGeneratedProvidedTypes = tcState.tcsCreatesGeneratedProvidedTypes || createsGeneratedProvidedTypes
- }
+ let tcState =
+ { tcState with
+ tcsTcSigEnv = tcSigEnv
+ tcsRootSigs = rootSigs
+ tcsCreatesGeneratedProvidedTypes =
+ tcState.tcsCreatesGeneratedProvidedTypes || createsGeneratedProvidedTypes
+ }
- partialResult, tcState)
+ partialResult, tcState)
+ )
| ParsedInput.ImplFile file ->
let qualNameOfFile = file.QualifiedName
@@ -1579,29 +1582,32 @@ let CheckOneInputWithCallback
)
return
- Finisher(fun tcState ->
- // Check if we've already seen an implementation for this fragment
- if Zset.contains qualNameOfFile tcState.tcsRootImpls then
- errorR (Error(FSComp.SR.buildImplementationAlreadyGiven (qualNameOfFile.Text), m))
+ Finisher(
+ node,
+ (fun tcState ->
+ // Check if we've already seen an implementation for this fragment
+ if Zset.contains qualNameOfFile tcState.tcsRootImpls then
+ errorR (Error(FSComp.SR.buildImplementationAlreadyGiven (qualNameOfFile.Text), m))
- let ccuSigForFile, fsTcState =
- AddCheckResultsToTcState
- (tcGlobals, amap, false, prefixPathOpt, tcSink, tcState.tcsTcImplEnv, qualNameOfFile, implFile.Signature)
- tcState
+ let ccuSigForFile, fsTcState =
+ AddCheckResultsToTcState
+ (tcGlobals, amap, false, prefixPathOpt, tcSink, tcState.tcsTcImplEnv, qualNameOfFile, implFile.Signature)
+ tcState
- let partialResult = tcEnvAtEnd, topAttrs, Some implFile, ccuSigForFile
+ let partialResult = tcEnvAtEnd, topAttrs, Some implFile, ccuSigForFile
- let tcState =
- { fsTcState with
- tcsCreatesGeneratedProvidedTypes =
- fsTcState.tcsCreatesGeneratedProvidedTypes || createsGeneratedProvidedTypes
- }
+ let tcState =
+ { fsTcState with
+ tcsCreatesGeneratedProvidedTypes =
+ fsTcState.tcsCreatesGeneratedProvidedTypes || createsGeneratedProvidedTypes
+ }
- partialResult, tcState)
+ partialResult, tcState)
+ )
with e ->
errorRecovery e range0
- return Finisher(fun tcState -> (tcState.TcEnvFromSignatures, EmptyTopAttrs, None, tcState.tcsCcuSig), tcState)
+ return Finisher(node, (fun tcState -> (tcState.TcEnvFromSignatures, EmptyTopAttrs, None, tcState.tcsCcuSig), tcState))
}
let AddSignatureResultToTcImplEnv (tcImports: TcImports, tcGlobals, prefixPathOpt, tcSink, tcState, input: ParsedInput) =
@@ -1626,6 +1632,106 @@ let AddSignatureResultToTcImplEnv (tcImports: TcImports, tcGlobals, prefixPathOp
partialResult, tcState
+module private TypeCheckingGraphProcessing =
+ open FSharp.Compiler.GraphChecking.GraphProcessing
+
+ // TODO Do we need to suppress some error logging if we
+ // TODO apply the same partial results multiple times?
+ // TODO Maybe we can enable logging only for the final fold
+ ///
+ /// Combine type-checking results of dependencies needed to type-check a 'higher' node in the graph
+ ///
+ /// Initial state
+ /// Direct dependencies of a node
+ /// Transitive dependencies of a node
+ /// A way to fold a single result into existing state
+ let private combineResults
+ (emptyState: State)
+ (deps: ProcessedNode> array)
+ (transitiveDeps: ProcessedNode> array)
+ (folder: State -> Finisher -> State)
+ : State =
+ match deps with
+ | [||] -> emptyState
+ | _ ->
+ // Instead of starting with empty state,
+ // reuse state produced by the dependency with the biggest number of transitive dependencies.
+ // This is to reduce the number of folds required to achieve the final state.
+ let biggestDependency =
+ let sizeMetric (node: ProcessedNode<_, _>) = node.Info.TransitiveDeps.Length
+ deps |> Array.maxBy sizeMetric
+
+ let firstState = biggestDependency.Result |> fst
+
+ // Find items not already included in the state.
+ let itemsPresent =
+ set
+ [|
+ yield! biggestDependency.Info.TransitiveDeps
+ yield biggestDependency.Info.Item
+ |]
+
+ let resultsToAdd =
+ transitiveDeps
+ |> Array.filter (fun dep -> itemsPresent.Contains dep.Info.Item = false)
+ |> Array.distinctBy (fun dep -> dep.Info.Item)
+ |> Array.sortWith (fun a b ->
+ // We preserve the order in which items are folded to the state.
+ match a.Info.Item, b.Info.Item with
+ | NodeToTypeCheck.PhysicalFile aIdx, NodeToTypeCheck.PhysicalFile bIdx
+ | NodeToTypeCheck.ArtificialImplFile aIdx, NodeToTypeCheck.ArtificialImplFile bIdx -> aIdx.CompareTo bIdx
+ | NodeToTypeCheck.PhysicalFile _, NodeToTypeCheck.ArtificialImplFile _ -> -1
+ | NodeToTypeCheck.ArtificialImplFile _, NodeToTypeCheck.PhysicalFile _ -> 1)
+ |> Array.map (fun dep -> dep.Result |> snd)
+
+ // Fold results not already included and produce the final state
+ let state = Array.fold folder firstState resultsToAdd
+ state
+
+ ///
+ /// Process a graph of items.
+ /// A version of 'GraphProcessing.processGraph' with a signature specific to type-checking.
+ ///
+ let processTypeCheckingGraph
+ (graph: Graph)
+ (work: NodeToTypeCheck -> State -> Finisher)
+ (emptyState: State)
+ (ct: CancellationToken)
+ : (int * FinalFileResult) list * State =
+
+ let workWrapper
+ (getProcessedNode: NodeToTypeCheck -> ProcessedNode>)
+ (node: NodeInfo)
+ : State * Finisher =
+ let folder (state: State) (Finisher (finisher = finisher)) : State = finisher state |> snd
+ let deps = node.Deps |> Array.except [| node.Item |] |> Array.map getProcessedNode
+
+ let transitiveDeps =
+ node.TransitiveDeps
+ |> Array.except [| node.Item |]
+ |> Array.map getProcessedNode
+
+ let inputState = combineResults emptyState deps transitiveDeps folder
+
+ let singleRes = work node.Item inputState
+ let state = folder inputState singleRes
+ state, singleRes
+
+ let results = processGraph graph workWrapper ct
+
+ let finalFileResults, state: (int * FinalFileResult) list * State =
+ (([], emptyState),
+ results
+ |> Array.choose (fun (item, res) ->
+ match item with
+ | NodeToTypeCheck.ArtificialImplFile _ -> None
+ | NodeToTypeCheck.PhysicalFile file -> Some(file, res)))
+ ||> Array.fold (fun (fileResults, state) (item, (_, Finisher (finisher = finisher))) ->
+ let fileResult, state = finisher state
+ (item, fileResult) :: fileResults, state)
+
+ finalFileResults, state
+
/// 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): 'a * (unit -> bool) * TcConfig * TcImports * TcGlobals * LongIdent option * TcState * (PhasedDiagnostic -> PhasedDiagnostic) * ParsedInput list)
@@ -1711,38 +1817,51 @@ let CheckMultipleInputsUsingGraphMode
// somewhere in the files processed prior to each one, or in the processing of this particular file.
let priorErrors = checkForErrors ()
- let processArtificialImplFile (input: ParsedInput) ((currentTcState, _currentPriorErrors): State) : Finisher =
- Finisher(fun (state: State) ->
- let tcState, currentPriorErrors = state
+ let processArtificialImplFile
+ (node: NodeToTypeCheck)
+ (input: ParsedInput)
+ ((currentTcState, _currentPriorErrors): State)
+ : Finisher =
+ Finisher(
+ node,
+ (fun (state: State) ->
+ let tcState, currentPriorErrors = state
- let f =
- // Retrieve the type-checked signature information and add it to the TcEnvFromImpls.
- AddSignatureResultToTcImplEnv(tcImports, tcGlobals, prefixPathOpt, TcResultsSink.NoSink, currentTcState, input)
+ let f =
+ // Retrieve the type-checked signature information and add it to the TcEnvFromImpls.
+ AddSignatureResultToTcImplEnv(tcImports, tcGlobals, prefixPathOpt, TcResultsSink.NoSink, currentTcState, input)
- // The `partialResult` will be excluded at the end of `GraphProcessing.processGraph`.
- // The important thing is that `nextTcState` will populated the necessary information to TcEnvFromImpls.
- let partialResult, nextTcState = f tcState
- partialResult, (nextTcState, currentPriorErrors))
+ // The `partialResult` will be excluded at the end of `GraphProcessing.processGraph`.
+ // The important thing is that `nextTcState` will populated the necessary information to TcEnvFromImpls.
+ let partialResult, nextTcState = f tcState
+ partialResult, (nextTcState, currentPriorErrors))
+ )
let processFile
+ (node: NodeToTypeCheck)
((input, logger): ParsedInput * DiagnosticsLogger)
((currentTcState, _currentPriorErrors): State)
- : Finisher =
+ : Finisher =
use _ = UseDiagnosticsLogger logger
let checkForErrors2 () = priorErrors || (logger.ErrorCount > 0)
let tcSink = TcResultsSink.NoSink
- let finisher =
- CheckOneInputWithCallback(checkForErrors2, tcConfig, tcImports, tcGlobals, prefixPathOpt, tcSink, currentTcState, input, false)
+ let (Finisher (finisher = finisher)) =
+ CheckOneInputWithCallback
+ node
+ (checkForErrors2, tcConfig, tcImports, tcGlobals, prefixPathOpt, tcSink, currentTcState, input, false)
|> Cancellable.runWithoutCancellation
- Finisher(fun (state: State) ->
- let tcState, priorErrors = state
- let (partialResult: PartialResult, tcState) = finisher.Invoke(tcState)
- let hasErrors = logger.ErrorCount > 0
- let priorOrCurrentErrors = priorErrors || hasErrors
- let state: State = tcState, priorOrCurrentErrors
- partialResult, state)
+ Finisher(
+ node,
+ (fun (state: State) ->
+ let tcState, priorErrors = state
+ let (partialResult: PartialResult, tcState) = finisher tcState
+ let hasErrors = logger.ErrorCount > 0
+ let priorOrCurrentErrors = priorErrors || hasErrors
+ let state: State = tcState, priorOrCurrentErrors
+ partialResult, state)
+ )
UseMultipleDiagnosticLoggers (inputs, diagnosticsLogger, Some eagerFormat) (fun inputsWithLoggers ->
// Equip loggers to locally filter w.r.t. scope pragmas in each input
@@ -1753,30 +1872,19 @@ let CheckMultipleInputsUsingGraphMode
let logger = DiagnosticsLoggerForInput(tcConfig, input, oldLogger)
input, logger)
- let processFile (node: NodeToTypeCheck) (state: State) : Finisher =
+ let processFile (node: NodeToTypeCheck) (state: State) : Finisher =
match node with
| NodeToTypeCheck.ArtificialImplFile idx ->
let parsedInput, _ = inputsWithLoggers[idx]
- processArtificialImplFile parsedInput state
+ processArtificialImplFile node parsedInput state
| NodeToTypeCheck.PhysicalFile idx ->
let parsedInput, logger = inputsWithLoggers[idx]
- processFile (parsedInput, logger) state
+ processFile node (parsedInput, logger) state
let state: State = tcState, priorErrors
- let finalStateItemChooser node =
- match node with
- | NodeToTypeCheck.ArtificialImplFile _ -> None
- | NodeToTypeCheck.PhysicalFile file -> Some file
-
let partialResults, (tcState, _) =
- TypeCheckingGraphProcessing.processTypeCheckingGraph
- nodeGraph
- processFile
- folder
- finalStateItemChooser
- state
- cts.Token
+ TypeCheckingGraphProcessing.processTypeCheckingGraph nodeGraph processFile state cts.Token
let partialResults =
partialResults
diff --git a/src/Compiler/FSharp.Compiler.Service.fsproj b/src/Compiler/FSharp.Compiler.Service.fsproj
index ecf060a63e..4e061b6724 100644
--- a/src/Compiler/FSharp.Compiler.Service.fsproj
+++ b/src/Compiler/FSharp.Compiler.Service.fsproj
@@ -415,8 +415,6 @@
-
-
diff --git a/src/Compiler/Facilities/DiagnosticsLogger.fs b/src/Compiler/Facilities/DiagnosticsLogger.fs
index 8fb5876b46..ef90d556a2 100644
--- a/src/Compiler/Facilities/DiagnosticsLogger.fs
+++ b/src/Compiler/Facilities/DiagnosticsLogger.fs
@@ -132,21 +132,21 @@ let ErrorWithSuggestions ((n, message), m, id, suggestions) =
let ErrorEnabledWithLanguageFeature ((n, message), m, enabledByLangFeature) =
DiagnosticEnabledWithLanguageFeature(n, message, m, enabledByLangFeature)
-let inline protectAssemblyExploration dflt f =
+let inline protectAssemblyExploration dflt ([] f) =
try
f ()
with
| UnresolvedPathReferenceNoRange _ -> dflt
| _ -> reraise ()
-let inline protectAssemblyExplorationF dflt f =
+let inline protectAssemblyExplorationF dflt ([] f) =
try
f ()
with
| UnresolvedPathReferenceNoRange (asmName, path) -> dflt (asmName, path)
| _ -> reraise ()
-let inline protectAssemblyExplorationNoReraise dflt1 dflt2 f =
+let inline protectAssemblyExplorationNoReraise dflt1 dflt2 ([] f) =
try
f ()
with
diff --git a/src/Compiler/SyntaxTree/LexFilter.fs b/src/Compiler/SyntaxTree/LexFilter.fs
index 09bc37e0c5..19f8eae953 100644
--- a/src/Compiler/SyntaxTree/LexFilter.fs
+++ b/src/Compiler/SyntaxTree/LexFilter.fs
@@ -1563,9 +1563,11 @@ type LexFilterImpl (
| INTERP_STRING_PART _ ->
pushCtxt tokenTup (CtxtParen (token, tokenTup.LexbufState.EndPos))
pushCtxtSeqBlock tokenTup NoAddBlockEnd
+ | INTERP_STRING_END _ -> ()
| _ ->
// Queue a dummy token at this position to check if any closing rules apply
delayToken(pool.UseLocation(tokenTup, ODUMMY token))
+
returnToken tokenLexbufState token
// Balancing rule. Encountering a 'end' can balance with a 'with' but only when not offside
diff --git a/src/Compiler/Utilities/illib.fs b/src/Compiler/Utilities/illib.fs
index 8dbf5fd453..244fd4608c 100644
--- a/src/Compiler/Utilities/illib.fs
+++ b/src/Compiler/Utilities/illib.fs
@@ -624,7 +624,7 @@ module ValueOptionInternal =
| Some x -> ValueSome x
| None -> ValueNone
- let inline bind f x =
+ let inline bind ([] f) x =
match x with
| ValueSome x -> f x
| ValueNone -> ValueNone
diff --git a/src/Compiler/Utilities/lib.fs b/src/Compiler/Utilities/lib.fs
index 61cbd7e1c8..f1e089350e 100755
--- a/src/Compiler/Utilities/lib.fs
+++ b/src/Compiler/Utilities/lib.fs
@@ -341,7 +341,7 @@ let nullableSlotFull x = x
type cache<'T> = { mutable cacheVal: 'T NonNullSlot }
let newCache() = { cacheVal = nullableSlotEmpty() }
-let inline cached cache resF =
+let inline cached cache ([] resF) =
match box cache.cacheVal with
| null ->
let res = resF()
@@ -350,7 +350,7 @@ let inline cached cache resF =
| _ ->
cache.cacheVal
-let inline cacheOptByref (cache: byref<'T option>) f =
+let inline cacheOptByref (cache: byref<'T option>) ([] f) =
match cache with
| Some v -> v
| None ->
@@ -361,7 +361,7 @@ let inline cacheOptByref (cache: byref<'T option>) f =
// REVIEW: this is only used because we want to mutate a record field,
// and because you cannot take a byref<_> of such a thing directly,
// we cannot use 'cacheOptByref'. If that is changed, this can be removed.
-let inline cacheOptRef (cache: _ ref) f =
+let inline cacheOptRef (cache: _ ref) ([] f) =
match cache.Value with
| Some v -> v
| None ->
diff --git a/src/FSharp.Core/observable.fs b/src/FSharp.Core/observable.fs
index eb34c62c85..2692d1435b 100644
--- a/src/FSharp.Core/observable.fs
+++ b/src/FSharp.Core/observable.fs
@@ -11,7 +11,7 @@ open Microsoft.FSharp.Control
[]
module Observable =
- let inline protect f succeed fail =
+ let inline protect ([] f) ([] succeed) ([] fail) =
match
(try
Choice1Of2(f ())
diff --git a/tests/FSharp.Compiler.ComponentTests/Language/InterpolatedStringsTests.fs b/tests/FSharp.Compiler.ComponentTests/Language/InterpolatedStringsTests.fs
index a3eed933be..16d70c1c15 100644
--- a/tests/FSharp.Compiler.ComponentTests/Language/InterpolatedStringsTests.fs
+++ b/tests/FSharp.Compiler.ComponentTests/Language/InterpolatedStringsTests.fs
@@ -109,4 +109,23 @@ let s = $"...%-%...{0}"
"""
|> compile
|> shouldFail
- |> withSingleDiagnostic (Warning 3376, Line 2, Col 9, Line 2, Col 24, "Bad format specifier: '%'")
\ No newline at end of file
+ |> withSingleDiagnostic (Warning 3376, Line 2, Col 9, Line 2, Col 24, "Bad format specifier: '%'")
+
+ []
+ let ``Interpolated expression can be offside`` () =
+ Fsx """
+let a() =
+ let b() =
+ $"
+{1}"
+ b()
+
+type Foo () =
+ member _.Bar () =
+ let x =
+ $"
+{2}"
+ x
+ """
+ |> compile
+ |> shouldSucceed
\ No newline at end of file
diff --git a/tests/service/data/SyntaxTree/String/InterpolatedStringOffsideInModule.fs b/tests/service/data/SyntaxTree/String/InterpolatedStringOffsideInModule.fs
new file mode 100644
index 0000000000..c8f1397e57
--- /dev/null
+++ b/tests/service/data/SyntaxTree/String/InterpolatedStringOffsideInModule.fs
@@ -0,0 +1,4 @@
+module A =
+ let b =
+ $"
+{0}"
\ No newline at end of file
diff --git a/tests/service/data/SyntaxTree/String/InterpolatedStringOffsideInModule.fs.bsl b/tests/service/data/SyntaxTree/String/InterpolatedStringOffsideInModule.fs.bsl
new file mode 100644
index 0000000000..7e6563219a
--- /dev/null
+++ b/tests/service/data/SyntaxTree/String/InterpolatedStringOffsideInModule.fs.bsl
@@ -0,0 +1,34 @@
+ImplFile
+ (ParsedImplFileInput
+ ("/root/String/InterpolatedStringOffsideInModule.fs", false,
+ QualifiedNameOfFile InterpolatedStringOffsideInModule, [], [],
+ [SynModuleOrNamespace
+ ([InterpolatedStringOffsideInModule], false, AnonModule,
+ [NestedModule
+ (SynComponentInfo
+ ([], None, [], [A],
+ PreXmlDoc ((1,0), FSharp.Compiler.Xml.XmlDocCollector), false,
+ None, (1,0--1,8)), false,
+ [Let
+ (false,
+ [SynBinding
+ (None, Normal, false, false, [],
+ PreXmlDoc ((2,4), FSharp.Compiler.Xml.XmlDocCollector),
+ SynValData
+ (None, SynValInfo ([], SynArgInfo ([], false, None)),
+ None),
+ Named (SynIdent (b, None), false, None, (2,8--2,9)), None,
+ InterpolatedString
+ ([String ("
+", (3,8--4,1));
+ FillExpr (Const (Int32 0, (4,1--4,2)), None);
+ String ("", (4,2--4,4))], Regular, (3,8--4,4)),
+ (2,8--2,9), Yes (2,4--4,4),
+ { LeadingKeyword = Let (2,4--2,7)
+ InlineKeyword = None
+ EqualsRange = Some (2,10--2,11) })], (2,4--4,4))], false,
+ (1,0--4,4), { ModuleKeyword = Some (1,0--1,6)
+ EqualsRange = Some (1,9--1,10) })], PreXmlDocEmpty,
+ [], None, (1,0--4,4), { LeadingKeyword = None })], (true, true),
+ { ConditionalDirectives = []
+ CodeComments = [] }, set []))
diff --git a/tests/service/data/SyntaxTree/String/InterpolatedStringOffsideInNestedLet.fs b/tests/service/data/SyntaxTree/String/InterpolatedStringOffsideInNestedLet.fs
new file mode 100644
index 0000000000..9c4e769bf7
--- /dev/null
+++ b/tests/service/data/SyntaxTree/String/InterpolatedStringOffsideInNestedLet.fs
@@ -0,0 +1,5 @@
+let a =
+ let b =
+ $"
+{0}"
+ b
\ No newline at end of file
diff --git a/tests/service/data/SyntaxTree/String/InterpolatedStringOffsideInNestedLet.fs.bsl b/tests/service/data/SyntaxTree/String/InterpolatedStringOffsideInNestedLet.fs.bsl
new file mode 100644
index 0000000000..d91bffd331
--- /dev/null
+++ b/tests/service/data/SyntaxTree/String/InterpolatedStringOffsideInNestedLet.fs.bsl
@@ -0,0 +1,40 @@
+ImplFile
+ (ParsedImplFileInput
+ ("/root/String/InterpolatedStringOffsideInNestedLet.fs", false,
+ QualifiedNameOfFile InterpolatedStringOffsideInNestedLet, [], [],
+ [SynModuleOrNamespace
+ ([InterpolatedStringOffsideInNestedLet], false, AnonModule,
+ [Let
+ (false,
+ [SynBinding
+ (None, Normal, false, false, [],
+ PreXmlDoc ((1,0), FSharp.Compiler.Xml.XmlDocCollector),
+ SynValData
+ (None, SynValInfo ([], SynArgInfo ([], false, None)), None),
+ Named (SynIdent (a, None), false, None, (1,4--1,5)), None,
+ LetOrUse
+ (false, false,
+ [SynBinding
+ (None, Normal, false, false, [],
+ PreXmlDoc ((2,4), FSharp.Compiler.Xml.XmlDocCollector),
+ SynValData
+ (None, SynValInfo ([], SynArgInfo ([], false, None)),
+ None),
+ Named (SynIdent (b, None), false, None, (2,8--2,9)),
+ None,
+ InterpolatedString
+ ([String ("
+", (3,8--4,1));
+ FillExpr (Const (Int32 0, (4,1--4,2)), None);
+ String ("", (4,2--4,4))], Regular, (3,8--4,4)),
+ (2,8--2,9), Yes (2,4--4,4),
+ { LeadingKeyword = Let (2,4--2,7)
+ InlineKeyword = None
+ EqualsRange = Some (2,10--2,11) })], Ident b,
+ (2,4--5,5), { InKeyword = None }), (1,4--1,5), NoneAtLet,
+ { LeadingKeyword = Let (1,0--1,3)
+ InlineKeyword = None
+ EqualsRange = Some (1,6--1,7) })], (1,0--5,5))],
+ PreXmlDocEmpty, [], None, (1,0--5,5), { LeadingKeyword = None })],
+ (true, true), { ConditionalDirectives = []
+ CodeComments = [] }, set []))
diff --git a/vsintegration/src/FSharp.Editor/CodeFixes/ConvertCSharpLambdaToFSharpLambda.fs b/vsintegration/src/FSharp.Editor/CodeFixes/ConvertCSharpLambdaToFSharpLambda.fs
index 978f8e328f..7028b9b5af 100644
--- a/vsintegration/src/FSharp.Editor/CodeFixes/ConvertCSharpLambdaToFSharpLambda.fs
+++ b/vsintegration/src/FSharp.Editor/CodeFixes/ConvertCSharpLambdaToFSharpLambda.fs
@@ -8,38 +8,57 @@ open System.Collections.Immutable
open Microsoft.CodeAnalysis.Text
open Microsoft.CodeAnalysis.CodeFixes
+open FSharp.Compiler.CodeAnalysis
+open FSharp.Compiler.Text
+
+open CancellableTasks
+
[]
type internal ConvertCSharpLambdaToFSharpLambdaCodeFixProvider [] () =
inherit CodeFixProvider()
static let title = SR.UseFSharpLambda()
- override _.FixableDiagnosticIds = ImmutableArray.Create("FS0039", "FS0043")
+ let tryGetSpans (parseResults: FSharpParseFileResults) (range: range) sourceText =
+ let flatten3 options =
+ match options with
+ | Some (Some a, Some b, Some c) -> Some(a, b, c)
+ | _ -> None
- override _.RegisterCodeFixesAsync context =
- asyncMaybe {
- let! parseResults =
- context.Document.GetFSharpParseResultsAsync(nameof (ConvertCSharpLambdaToFSharpLambdaCodeFixProvider))
- |> liftAsync
+ parseResults.TryRangeOfParenEnclosingOpEqualsGreaterUsage range.Start
+ |> Option.map (fun (fullParenRange, lambdaArgRange, lambdaBodyRange) ->
+ RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, fullParenRange),
+ RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, lambdaArgRange),
+ RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, lambdaBodyRange))
+ |> flatten3
- let! sourceText = context.Document.GetTextAsync(context.CancellationToken)
+ override _.FixableDiagnosticIds = ImmutableArray.Create("FS0039")
- let errorRange =
- RoslynHelpers.TextSpanToFSharpRange(context.Document.FilePath, context.Span, sourceText)
+ override this.RegisterCodeFixesAsync context = context.RegisterFsharpFix(this)
- let! fullParenRange, lambdaArgRange, lambdaBodyRange =
- parseResults.TryRangeOfParenEnclosingOpEqualsGreaterUsage errorRange.Start
+ interface IFSharpCodeFixProvider with
+ member _.GetCodeFixIfAppliesAsync document span =
+ cancellableTask {
+ let! cancellationToken = CancellableTask.getCurrentCancellationToken ()
- let! fullParenSpan = RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, fullParenRange)
- let! lambdaArgSpan = RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, lambdaArgRange)
- let! lambdaBodySpan = RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, lambdaBodyRange)
+ let! parseResults = document.GetFSharpParseResultsAsync(nameof (ConvertCSharpLambdaToFSharpLambdaCodeFixProvider))
- let replacement =
- let argText = sourceText.GetSubText(lambdaArgSpan).ToString()
- let bodyText = sourceText.GetSubText(lambdaBodySpan).ToString()
- TextChange(fullParenSpan, "fun " + argText + " -> " + bodyText)
+ let! sourceText = document.GetTextAsync(cancellationToken)
- do context.RegisterFsharpFix(CodeFix.ConvertCSharpLambdaToFSharpLambda, title, [| replacement |])
- }
- |> Async.Ignore
- |> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken)
+ let errorRange =
+ RoslynHelpers.TextSpanToFSharpRange(document.FilePath, span, sourceText)
+
+ return
+ tryGetSpans parseResults errorRange sourceText
+ |> Option.map (fun (fullParenSpan, lambdaArgSpan, lambdaBodySpan) ->
+ let replacement =
+ let argText = sourceText.GetSubText(lambdaArgSpan).ToString()
+ let bodyText = sourceText.GetSubText(lambdaBodySpan).ToString()
+ TextChange(fullParenSpan, "fun " + argText + " -> " + bodyText)
+
+ {
+ Name = CodeFix.ConvertCSharpLambdaToFSharpLambda
+ Message = title
+ Changes = [ replacement ]
+ })
+ }
diff --git a/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/CodeFixTestFramework.fs b/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/CodeFixTestFramework.fs
index 616bc907fe..542039ccc5 100644
--- a/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/CodeFixTestFramework.fs
+++ b/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/CodeFixTestFramework.fs
@@ -20,7 +20,7 @@ let getRelevantDiagnostic (document: Document) errorNumber =
return
checkFileResults.Diagnostics
|> Seq.where (fun d -> d.ErrorNumber = errorNumber)
- |> Seq.exactlyOne
+ |> Seq.head
}
let tryFix (code: string) diagnostic (fixProvider: IFSharpCodeFixProvider) =
diff --git a/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/ConvertCSharpLambdaToFSharpLambdaTests.fs b/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/ConvertCSharpLambdaToFSharpLambdaTests.fs
new file mode 100644
index 0000000000..3c66ceb537
--- /dev/null
+++ b/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/ConvertCSharpLambdaToFSharpLambdaTests.fs
@@ -0,0 +1,46 @@
+// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information.
+
+module FSharp.Editor.Tests.CodeFixes.ConvertCSharpLambdaToFSharpLambdaTests
+
+open Microsoft.VisualStudio.FSharp.Editor
+open Xunit
+
+open CodeFixTestFramework
+
+let private codeFix = ConvertCSharpLambdaToFSharpLambdaCodeFixProvider()
+
+let private diagnostic = 0039 // Something is not defined
+
+[]
+let ``Fixes FS0039 for lambdas`` () =
+ let code =
+ """
+let incAll = List.map (n => n + 1)
+"""
+
+ let expected =
+ Some
+ {
+ Message = "Use F# lambda syntax"
+ FixedCode =
+ """
+let incAll = List.map (fun n -> n + 1)
+"""
+ }
+
+ let actual = codeFix |> tryFix code diagnostic
+
+ Assert.Equal(expected, actual)
+
+[]
+let ``Doesn't fix FS0039 for random undefined stuff`` () =
+ let code =
+ """
+let f = g
+"""
+
+ let expected = None
+
+ let actual = codeFix |> tryFix code diagnostic
+
+ Assert.Equal(expected, actual)
diff --git a/vsintegration/tests/FSharp.Editor.Tests/FSharp.Editor.Tests.fsproj b/vsintegration/tests/FSharp.Editor.Tests/FSharp.Editor.Tests.fsproj
index a943a2932c..47be31b465 100644
--- a/vsintegration/tests/FSharp.Editor.Tests/FSharp.Editor.Tests.fsproj
+++ b/vsintegration/tests/FSharp.Editor.Tests/FSharp.Editor.Tests.fsproj
@@ -38,6 +38,7 @@
+