Merge pull request #15511 from dotnet/merges/main-to-release/dev17.7

Merge main to release/dev17.7
This commit is contained in:
Vlad Zarytovskii 2023-06-28 01:55:03 +02:00 коммит произвёл GitHub
Родитель 7966421cd1 3ea571c54c
Коммит a2e47db165
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
22 изменённых файлов: 383 добавлений и 227 удалений

1
.gitattributes поставляемый
Просмотреть файл

@ -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}"

Просмотреть файл

@ -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

Просмотреть файл

@ -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" />