зеркало из https://github.com/dotnet/fsharp.git
Merge pull request #15511 from dotnet/merges/main-to-release/dev17.7
Merge main to release/dev17.7
This commit is contained in:
Коммит
a2e47db165
|
@ -28,5 +28,6 @@ targets.make text eol=lf
|
|||
*.sh text eol=lf
|
||||
|
||||
*.bsl linguist-vendored=true
|
||||
*.xlf linguist-generated=true
|
||||
|
||||
*.png binary
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
/// <summary>
|
||||
/// Combine type-checking results of dependencies needed to type-check a 'higher' node in the graph
|
||||
/// </summary>
|
||||
/// <param name="emptyState">Initial state</param>
|
||||
/// <param name="deps">Direct dependencies of a node</param>
|
||||
/// <param name="transitiveDeps">Transitive dependencies of a node</param>
|
||||
/// <param name="folder">A way to fold a single result into existing state</param>
|
||||
/// <remarks>
|
||||
/// Similar to 'processFileGraph', this function is generic yet specific to the type-checking process.
|
||||
/// </remarks>
|
||||
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.
|
||||
/// <summary>
|
||||
/// Process a graph of items.
|
||||
/// A version of 'GraphProcessing.processGraph' with a signature slightly specific to type-checking.
|
||||
/// </summary>
|
||||
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
|
|
@ -1,17 +0,0 @@
|
|||
/// Parallel processing of a type-checking file graph.
|
||||
module internal FSharp.Compiler.GraphChecking.TypeCheckingGraphProcessing
|
||||
|
||||
open System.Threading
|
||||
|
||||
/// <summary>
|
||||
/// Process a graph of items.
|
||||
/// A version of 'GraphProcessing.processGraph' with a signature slightly specific to type-checking.
|
||||
/// </summary>
|
||||
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
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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<State, FinalFileResult>) : FinalFileResult * State = finisher.Invoke(state)
|
||||
|
||||
/// Typecheck a single file (or interactive entry into F# Interactive)
|
||||
/// <returns>a callback functions that takes a `TcState` and will add the checked result to it.</returns>
|
||||
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<Finisher<TcState, PartialResult>> =
|
||||
: Cancellable<Finisher<NodeToTypeCheck, TcState, PartialResult>> =
|
||||
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
|
||||
/// <summary>
|
||||
/// Combine type-checking results of dependencies needed to type-check a 'higher' node in the graph
|
||||
/// </summary>
|
||||
/// <param name="emptyState">Initial state</param>
|
||||
/// <param name="deps">Direct dependencies of a node</param>
|
||||
/// <param name="transitiveDeps">Transitive dependencies of a node</param>
|
||||
/// <param name="folder">A way to fold a single result into existing state</param>
|
||||
let private combineResults
|
||||
(emptyState: State)
|
||||
(deps: ProcessedNode<NodeToTypeCheck, State * Finisher<NodeToTypeCheck, State, FinalFileResult>> array)
|
||||
(transitiveDeps: ProcessedNode<NodeToTypeCheck, State * Finisher<NodeToTypeCheck, State, FinalFileResult>> array)
|
||||
(folder: State -> Finisher<NodeToTypeCheck, 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.
|
||||
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
|
||||
|
||||
/// <summary>
|
||||
/// Process a graph of items.
|
||||
/// A version of 'GraphProcessing.processGraph' with a signature specific to type-checking.
|
||||
/// </summary>
|
||||
let processTypeCheckingGraph
|
||||
(graph: Graph<NodeToTypeCheck>)
|
||||
(work: NodeToTypeCheck -> State -> Finisher<NodeToTypeCheck, State, FinalFileResult>)
|
||||
(emptyState: State)
|
||||
(ct: CancellationToken)
|
||||
: (int * FinalFileResult) list * State =
|
||||
|
||||
let workWrapper
|
||||
(getProcessedNode: NodeToTypeCheck -> ProcessedNode<NodeToTypeCheck, State * Finisher<NodeToTypeCheck, State, FinalFileResult>>)
|
||||
(node: NodeInfo<NodeToTypeCheck>)
|
||||
: State * Finisher<NodeToTypeCheck, State, FinalFileResult> =
|
||||
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<State, PartialResult> =
|
||||
Finisher(fun (state: State) ->
|
||||
let tcState, currentPriorErrors = state
|
||||
let processArtificialImplFile
|
||||
(node: NodeToTypeCheck)
|
||||
(input: ParsedInput)
|
||||
((currentTcState, _currentPriorErrors): State)
|
||||
: Finisher<NodeToTypeCheck, State, PartialResult> =
|
||||
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<State, PartialResult> =
|
||||
: Finisher<NodeToTypeCheck, State, PartialResult> =
|
||||
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<State, PartialResult> =
|
||||
let processFile (node: NodeToTypeCheck) (state: State) : Finisher<NodeToTypeCheck, State, PartialResult> =
|
||||
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<NodeToTypeCheck, int, State, FinalFileResult>
|
||||
nodeGraph
|
||||
processFile
|
||||
folder
|
||||
finalStateItemChooser
|
||||
state
|
||||
cts.Token
|
||||
TypeCheckingGraphProcessing.processTypeCheckingGraph nodeGraph processFile state cts.Token
|
||||
|
||||
let partialResults =
|
||||
partialResults
|
||||
|
|
|
@ -415,8 +415,6 @@
|
|||
<Compile Include="Driver\GraphChecking\DependencyResolution.fs" />
|
||||
<Compile Include="Driver\GraphChecking\GraphProcessing.fsi" />
|
||||
<Compile Include="Driver\GraphChecking\GraphProcessing.fs" />
|
||||
<Compile Include="Driver\GraphChecking\TypeCheckingGraphProcessing.fsi" />
|
||||
<Compile Include="Driver\GraphChecking\TypeCheckingGraphProcessing.fs" />
|
||||
<Content Include="Driver\GraphChecking\Docs.md" />
|
||||
<Compile Include="Driver\ParseAndCheckInputs.fsi" />
|
||||
<Compile Include="Driver\ParseAndCheckInputs.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 ([<InlineIfLambda>] f) =
|
||||
try
|
||||
f ()
|
||||
with
|
||||
| UnresolvedPathReferenceNoRange _ -> dflt
|
||||
| _ -> reraise ()
|
||||
|
||||
let inline protectAssemblyExplorationF dflt f =
|
||||
let inline protectAssemblyExplorationF dflt ([<InlineIfLambda>] f) =
|
||||
try
|
||||
f ()
|
||||
with
|
||||
| UnresolvedPathReferenceNoRange (asmName, path) -> dflt (asmName, path)
|
||||
| _ -> reraise ()
|
||||
|
||||
let inline protectAssemblyExplorationNoReraise dflt1 dflt2 f =
|
||||
let inline protectAssemblyExplorationNoReraise dflt1 dflt2 ([<InlineIfLambda>] f) =
|
||||
try
|
||||
f ()
|
||||
with
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -624,7 +624,7 @@ module ValueOptionInternal =
|
|||
| Some x -> ValueSome x
|
||||
| None -> ValueNone
|
||||
|
||||
let inline bind f x =
|
||||
let inline bind ([<InlineIfLambda>] f) x =
|
||||
match x with
|
||||
| ValueSome x -> f x
|
||||
| ValueNone -> ValueNone
|
||||
|
|
|
@ -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 ([<InlineIfLambda>] 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>) ([<InlineIfLambda>] 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) ([<InlineIfLambda>] f) =
|
||||
match cache.Value with
|
||||
| Some v -> v
|
||||
| None ->
|
||||
|
|
|
@ -11,7 +11,7 @@ open Microsoft.FSharp.Control
|
|||
[<RequireQualifiedAccess>]
|
||||
module Observable =
|
||||
|
||||
let inline protect f succeed fail =
|
||||
let inline protect ([<InlineIfLambda>] f) ([<InlineIfLambda>] succeed) ([<InlineIfLambda>] fail) =
|
||||
match
|
||||
(try
|
||||
Choice1Of2(f ())
|
||||
|
|
|
@ -110,3 +110,22 @@ let s = $"...%-%...{0}"
|
|||
|> compile
|
||||
|> shouldFail
|
||||
|> withSingleDiagnostic (Warning 3376, Line 2, Col 9, Line 2, Col 24, "Bad format specifier: '%'")
|
||||
|
||||
[<Fact>]
|
||||
let ``Interpolated expression can be offside`` () =
|
||||
Fsx """
|
||||
let a() =
|
||||
let b() =
|
||||
$"
|
||||
{1}"
|
||||
b()
|
||||
|
||||
type Foo () =
|
||||
member _.Bar () =
|
||||
let x =
|
||||
$"
|
||||
{2}"
|
||||
x
|
||||
"""
|
||||
|> compile
|
||||
|> shouldSucceed
|
|
@ -0,0 +1,4 @@
|
|||
module A =
|
||||
let b =
|
||||
$"
|
||||
{0}"
|
34
tests/service/data/SyntaxTree/String/InterpolatedStringOffsideInModule.fs.bsl
поставляемый
Normal file
34
tests/service/data/SyntaxTree/String/InterpolatedStringOffsideInModule.fs.bsl
поставляемый
Normal file
|
@ -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 []))
|
|
@ -0,0 +1,5 @@
|
|||
let a =
|
||||
let b =
|
||||
$"
|
||||
{0}"
|
||||
b
|
40
tests/service/data/SyntaxTree/String/InterpolatedStringOffsideInNestedLet.fs.bsl
поставляемый
Normal file
40
tests/service/data/SyntaxTree/String/InterpolatedStringOffsideInNestedLet.fs.bsl
поставляемый
Normal file
|
@ -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 []))
|
|
@ -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
|
||||
|
||||
[<ExportCodeFixProvider(FSharpConstants.FSharpLanguageName, Name = CodeFix.ConvertCSharpLambdaToFSharpLambda); Shared>]
|
||||
type internal ConvertCSharpLambdaToFSharpLambdaCodeFixProvider [<ImportingConstructor>] () =
|
||||
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 ]
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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) =
|
||||
|
|
|
@ -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
|
||||
|
||||
[<Fact>]
|
||||
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)
|
||||
|
||||
[<Fact>]
|
||||
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)
|
|
@ -38,6 +38,7 @@
|
|||
<Compile Include="CodeFixes\ChangeToUpcastTests.fs" />
|
||||
<Compile Include="CodeFixes\AddMissingRecToMutuallyRecFunctionsTests.fs" />
|
||||
<Compile Include="CodeFixes\WrapExpressionInParenthesesTests.fs" />
|
||||
<Compile Include="CodeFixes\ConvertCSharpLambdaToFSharpLambdaTests.fs" />
|
||||
<Compile Include="CodeFixes\RemoveReturnOrYieldTests.fs" />
|
||||
<Compile Include="Hints\HintTestFramework.fs" />
|
||||
<Compile Include="Hints\OptionParserTests.fs" />
|
||||
|
|
Загрузка…
Ссылка в новой задаче