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