diff --git a/src/Compiler/Checking/CheckDeclarations.fs b/src/Compiler/Checking/CheckDeclarations.fs index 285bc56a614..7ea1c4b3429 100644 --- a/src/Compiler/Checking/CheckDeclarations.fs +++ b/src/Compiler/Checking/CheckDeclarations.fs @@ -5309,17 +5309,17 @@ let CheckOneImplFile synImplFile, diagnosticOptions) = - let (ParsedImplFileInput (fileName, isScript, qualNameOfFile, scopedPragmas, _, implFileFrags, isLastCompiland, _)) = synImplFile + let (ParsedImplFileInput (fileName, isScript, qualNameOfFile, scopedPragmas, _, implFileFrags, isLastCompiland, _, _)) = synImplFile let infoReader = InfoReader(g, amap) cancellable { - use _ = - Activity.start "CheckDeclarations.CheckOneImplFile" + use _ = + Activity.start "CheckDeclarations.CheckOneImplFile" [| "fileName", fileName "qualifiedNameOfFile", qualNameOfFile.Text |] - let cenv = + let cenv = cenv.Create (g, isScript, amap, thisCcu, false, Option.isSome rootSigOpt, conditionalDefines, tcSink, (LightweightTcValForUsingInBuildMethodCall g), isInternalTestSpanStackReferring, diagnosticOptions, @@ -5445,7 +5445,7 @@ let CheckOneImplFile /// Check an entire signature file -let CheckOneSigFile (g, amap, thisCcu, checkForErrors, conditionalDefines, tcSink, isInternalTestSpanStackReferring, diagnosticOptions) tcEnv (sigFile: ParsedSigFileInput) = +let CheckOneSigFile (g, amap, thisCcu, checkForErrors, conditionalDefines, tcSink, isInternalTestSpanStackReferring, diagnosticOptions) tcEnv (sigFile: ParsedSigFileInput) = cancellable { use _ = Activity.start "CheckDeclarations.CheckOneSigFile" @@ -5453,7 +5453,7 @@ let CheckOneSigFile (g, amap, thisCcu, checkForErrors, conditionalDefines, tcSin "fileName", sigFile.FileName "qualifiedNameOfFile", sigFile.QualifiedName.Text |] - let cenv = + let cenv = cenv.Create (g, false, amap, thisCcu, true, false, conditionalDefines, tcSink, (LightweightTcValForUsingInBuildMethodCall g), isInternalTestSpanStackReferring, diff --git a/src/Compiler/Driver/ParseAndCheckInputs.fs b/src/Compiler/Driver/ParseAndCheckInputs.fs index 58fc5c9ac34..ac36b76477c 100644 --- a/src/Compiler/Driver/ParseAndCheckInputs.fs +++ b/src/Compiler/Driver/ParseAndCheckInputs.fs @@ -93,13 +93,13 @@ let PrependPathToSpec x (SynModuleOrNamespaceSig (longId, isRecursive, kind, dec let PrependPathToInput x inp = match inp with - | ParsedInput.ImplFile (ParsedImplFileInput (b, c, q, d, hd, impls, e, trivia)) -> + | ParsedInput.ImplFile (ParsedImplFileInput (b, c, q, d, hd, impls, e, trivia, i)) -> ParsedInput.ImplFile( - ParsedImplFileInput(b, c, PrependPathToQualFileName x q, d, hd, List.map (PrependPathToImpl x) impls, e, trivia) + ParsedImplFileInput(b, c, PrependPathToQualFileName x q, d, hd, List.map (PrependPathToImpl x) impls, e, trivia, i) ) - | ParsedInput.SigFile (ParsedSigFileInput (b, q, d, hd, specs, trivia)) -> - ParsedInput.SigFile(ParsedSigFileInput(b, PrependPathToQualFileName x q, d, hd, List.map (PrependPathToSpec x) specs, trivia)) + | ParsedInput.SigFile (ParsedSigFileInput (b, q, d, hd, specs, trivia, i)) -> + ParsedInput.SigFile(ParsedSigFileInput(b, PrependPathToQualFileName x q, d, hd, List.map (PrependPathToSpec x) specs, trivia, i)) let IsValidAnonModuleName (modname: string) = modname |> String.forall (fun c -> Char.IsLetterOrDigit c || c = '_') @@ -244,7 +244,8 @@ let PostParseModuleImpls isLastCompiland, ParsedImplFile (hashDirectives, impls), lexbuf: UnicodeLexing.Lexbuf, - tripleSlashComments: range list + tripleSlashComments: range list, + identifiers: Set ) = let othersWithSameName = impls @@ -284,7 +285,9 @@ let PostParseModuleImpls CodeComments = codeComments } - ParsedInput.ImplFile(ParsedImplFileInput(fileName, isScript, qualName, scopedPragmas, hashDirectives, impls, isLastCompiland, trivia)) + ParsedInput.ImplFile( + ParsedImplFileInput(fileName, isScript, qualName, scopedPragmas, hashDirectives, impls, isLastCompiland, trivia, identifiers) + ) let PostParseModuleSpecs ( @@ -293,7 +296,8 @@ let PostParseModuleSpecs isLastCompiland, ParsedSigFile (hashDirectives, specs), lexbuf: UnicodeLexing.Lexbuf, - tripleSlashComments: range list + tripleSlashComments: range list, + identifiers: Set ) = let othersWithSameName = specs @@ -332,7 +336,7 @@ let PostParseModuleSpecs CodeComments = codeComments } - ParsedInput.SigFile(ParsedSigFileInput(fileName, qualName, scopedPragmas, hashDirectives, specs, trivia)) + ParsedInput.SigFile(ParsedSigFileInput(fileName, qualName, scopedPragmas, hashDirectives, specs, trivia, identifiers)) type ModuleNamesDict = Map> @@ -377,26 +381,26 @@ let DeduplicateModuleName (moduleNamesDict: ModuleNamesDict) fileName (qualNameO let DeduplicateParsedInputModuleName (moduleNamesDict: ModuleNamesDict) input = match input with | ParsedInput.ImplFile implFile -> - let (ParsedImplFileInput (fileName, isScript, qualNameOfFile, scopedPragmas, hashDirectives, modules, flags, trivia)) = + let (ParsedImplFileInput (fileName, isScript, qualNameOfFile, scopedPragmas, hashDirectives, modules, flags, trivia, identifiers)) = implFile let qualNameOfFileR, moduleNamesDictR = DeduplicateModuleName moduleNamesDict fileName qualNameOfFile let implFileR = - ParsedImplFileInput(fileName, isScript, qualNameOfFileR, scopedPragmas, hashDirectives, modules, flags, trivia) + ParsedImplFileInput(fileName, isScript, qualNameOfFileR, scopedPragmas, hashDirectives, modules, flags, trivia, identifiers) let inputR = ParsedInput.ImplFile implFileR inputR, moduleNamesDictR | ParsedInput.SigFile sigFile -> - let (ParsedSigFileInput (fileName, qualNameOfFile, scopedPragmas, hashDirectives, modules, trivia)) = + let (ParsedSigFileInput (fileName, qualNameOfFile, scopedPragmas, hashDirectives, modules, trivia, identifiers)) = sigFile let qualNameOfFileR, moduleNamesDictR = DeduplicateModuleName moduleNamesDict fileName qualNameOfFile let sigFileR = - ParsedSigFileInput(fileName, qualNameOfFileR, scopedPragmas, hashDirectives, modules, trivia) + ParsedSigFileInput(fileName, qualNameOfFileR, scopedPragmas, hashDirectives, modules, trivia, identifiers) let inputT = ParsedInput.SigFile sigFileR inputT, moduleNamesDictR @@ -427,6 +431,28 @@ let ParseInput try let input = + let identStore = HashSet() + + let identCaptureLexer x = + let token = lexer x + + match token with + | Parser.token.PERCENT_OP ident + | Parser.token.FUNKY_OPERATOR_NAME ident + | Parser.token.ADJACENT_PREFIX_OP ident + | Parser.token.PLUS_MINUS_OP ident + | Parser.token.INFIX_AMP_OP ident + | Parser.token.INFIX_STAR_DIV_MOD_OP ident + | Parser.token.PREFIX_OP ident + | Parser.token.INFIX_BAR_OP ident + | Parser.token.INFIX_AT_HAT_OP ident + | Parser.token.INFIX_COMPARE_OP ident + | Parser.token.INFIX_STAR_STAR_OP ident + | Parser.token.IDENT ident -> identStore.Add ident |> ignore + | _ -> () + + token + if FSharpMLCompatFileSuffixes |> List.exists (FileSystemUtils.checkSuffix fileName) then if lexbuf.SupportsFeature LanguageFeature.MLCompatRevisions then errorR (Error(FSComp.SR.buildInvalidSourceFileExtensionML fileName, rangeStartup)) @@ -435,19 +461,19 @@ let ParseInput // Call the appropriate parser - for signature files or implementation files if FSharpImplFileSuffixes |> List.exists (FileSystemUtils.checkSuffix fileName) then - let impl = Parser.implementationFile lexer lexbuf + let impl = Parser.implementationFile identCaptureLexer lexbuf let tripleSlashComments = LexbufLocalXmlDocStore.ReportInvalidXmlDocPositions(lexbuf) - PostParseModuleImpls(defaultNamespace, fileName, isLastCompiland, impl, lexbuf, tripleSlashComments) + PostParseModuleImpls(defaultNamespace, fileName, isLastCompiland, impl, lexbuf, tripleSlashComments, Set identStore) elif FSharpSigFileSuffixes |> List.exists (FileSystemUtils.checkSuffix fileName) then - let intfs = Parser.signatureFile lexer lexbuf + let intfs = Parser.signatureFile identCaptureLexer lexbuf let tripleSlashComments = LexbufLocalXmlDocStore.ReportInvalidXmlDocPositions(lexbuf) - PostParseModuleSpecs(defaultNamespace, fileName, isLastCompiland, intfs, lexbuf, tripleSlashComments) + PostParseModuleSpecs(defaultNamespace, fileName, isLastCompiland, intfs, lexbuf, tripleSlashComments, Set identStore) else if lexbuf.SupportsFeature LanguageFeature.MLCompatRevisions then error (Error(FSComp.SR.buildInvalidSourceFileExtensionUpdated fileName, rangeStartup)) else @@ -519,7 +545,8 @@ let EmptyParsedInput (fileName, isLastCompiland) = { ConditionalDirectives = [] CodeComments = [] - } + }, + Set.empty ) ) else @@ -535,7 +562,8 @@ let EmptyParsedInput (fileName, isLastCompiland) = { ConditionalDirectives = [] CodeComments = [] - } + }, + Set.empty ) ) diff --git a/src/Compiler/Driver/ScriptClosure.fs b/src/Compiler/Driver/ScriptClosure.fs index 74a4c083a7d..00d38e1f234 100644 --- a/src/Compiler/Driver/ScriptClosure.fs +++ b/src/Compiler/Driver/ScriptClosure.fs @@ -515,13 +515,23 @@ module ScriptPreprocessClosure = match lastParsedInput with | Some (ParsedInput.ImplFile lastParsedImplFile) -> - let (ParsedImplFileInput (name, isScript, qualNameOfFile, scopedPragmas, hashDirectives, implFileFlags, _, trivia)) = + let (ParsedImplFileInput (name, isScript, qualNameOfFile, scopedPragmas, hashDirectives, implFileFlags, _, trivia, identifiers)) = lastParsedImplFile let isLastCompiland = (true, tcConfig.target.IsExe) let lastParsedImplFileR = - ParsedImplFileInput(name, isScript, qualNameOfFile, scopedPragmas, hashDirectives, implFileFlags, isLastCompiland, trivia) + ParsedImplFileInput( + name, + isScript, + qualNameOfFile, + scopedPragmas, + hashDirectives, + implFileFlags, + isLastCompiland, + trivia, + identifiers + ) let lastClosureFileR = ClosureFile(fileName, m, Some(ParsedInput.ImplFile lastParsedImplFileR), parseDiagnostics, metaDiagnostics, nowarns) diff --git a/src/Compiler/Interactive/fsi.fs b/src/Compiler/Interactive/fsi.fs index 5660f844d71..6a1c4b77c04 100644 --- a/src/Compiler/Interactive/fsi.fs +++ b/src/Compiler/Interactive/fsi.fs @@ -1850,7 +1850,7 @@ type internal FsiDynamicCompiler( let impl = SynModuleOrNamespace(prefix,false, SynModuleOrNamespaceKind.NamedModule,defs,PreXmlDoc.Empty,[],None,m, { LeadingKeyword = SynModuleOrNamespaceLeadingKeyword.None }) let isLastCompiland = true let isExe = false - let input = ParsedInput.ImplFile (ParsedImplFileInput (fileName,true, ComputeQualifiedNameOfFileFromUniquePath (m,prefixPath),[],[],[impl],(isLastCompiland, isExe), { ConditionalDirectives = []; CodeComments = [] })) + let input = ParsedInput.ImplFile (ParsedImplFileInput (fileName,true, ComputeQualifiedNameOfFileFromUniquePath (m,prefixPath),[],[],[impl],(isLastCompiland, isExe), { ConditionalDirectives = []; CodeComments = [] }, Set.empty)) let isIncrementalFragment = true let istate,tcEnvAtEndOfLastInput,declaredImpls = ProcessInputs (ctok, diagnosticsLogger, istate, [input], showTypes, isIncrementalFragment, isInteractiveItExpr, prefix, m) let tcState = istate.tcState diff --git a/src/Compiler/Service/IncrementalBuild.fs b/src/Compiler/Service/IncrementalBuild.fs index b8d9e4f648c..578eb5558f0 100644 --- a/src/Compiler/Service/IncrementalBuild.fs +++ b/src/Compiler/Service/IncrementalBuild.fs @@ -142,7 +142,8 @@ module IncrementalBuildSyntaxTree = [], [], isLastCompiland, - { ConditionalDirectives = []; CodeComments = [] } + { ConditionalDirectives = []; CodeComments = [] }, + Set.empty ) ) else @@ -292,7 +293,8 @@ type BoundModel private (tcConfig: TcConfig, GraphNode(node { match! this.TypeCheck(false) with | FullState(tcInfo, tcInfoExtras) -> return tcInfo, tcInfoExtras - | PartialState(tcInfo) -> return tcInfo, emptyTcInfoExtras + | PartialState(tcInfo) -> + return tcInfo, emptyTcInfoExtras }) let partialGraphNode = diff --git a/src/Compiler/Service/SemanticClassificationKey.fs b/src/Compiler/Service/SemanticClassificationKey.fs index c6b1279e762..6613dce2044 100644 --- a/src/Compiler/Service/SemanticClassificationKey.fs +++ b/src/Compiler/Service/SemanticClassificationKey.fs @@ -56,8 +56,13 @@ type SemanticClassificationKeyStoreBuilder() = let b = BlobBuilder() member _.WriteAll(semanticClassification: SemanticClassificationItem[]) = - use ptr = fixed semanticClassification - b.WriteBytes(NativePtr.ofNativeInt (NativePtr.toNativeInt ptr), semanticClassification.Length * sizeof) + if semanticClassification.Length > 0 then + use ptr = fixed semanticClassification + + b.WriteBytes( + NativePtr.ofNativeInt (NativePtr.toNativeInt ptr), + semanticClassification.Length * sizeof + ) member _.TryBuildAndReset() = if b.Count > 0 then diff --git a/src/Compiler/Service/service.fs b/src/Compiler/Service/service.fs index 90215b6a8f5..07c1f518725 100644 --- a/src/Compiler/Service/service.fs +++ b/src/Compiler/Service/service.fs @@ -81,6 +81,18 @@ module Helpers = let AreSubsumable3 ((fileName1: string, _, o1: FSharpProjectOptions), (fileName2: string, _, o2: FSharpProjectOptions)) = (fileName1 = fileName2) && FSharpProjectOptions.UseSameProject(o1, o2) + /// If a symbol is an attribute check if given set of names contains its name without the Attribute suffix + let rec NamesContainAttribute (symbol: FSharpSymbol) names = + match symbol with + | :? FSharpMemberOrFunctionOrValue as mofov -> + mofov.DeclaringEntity + |> Option.map (fun entity -> NamesContainAttribute entity names) + |> Option.defaultValue false + | :? FSharpEntity as entity when entity.IsAttributeType && symbol.DisplayNameCore.EndsWithOrdinal "Attribute" -> + let nameWithoutAttribute = String.dropSuffix symbol.DisplayNameCore "Attribute" + names |> Set.contains nameWithoutAttribute + | _ -> false + module CompileHelpers = let mkCompilationDiagnosticsHandlers () = let diagnostics = ResizeArray<_>() @@ -1445,12 +1457,26 @@ type FSharpChecker options: FSharpProjectOptions, symbol: FSharpSymbol, ?canInvalidateProject: bool, + ?fastCheck: bool, ?userOpName: string ) = let canInvalidateProject = defaultArg canInvalidateProject true let userOpName = defaultArg userOpName "Unknown" - backgroundCompiler.FindReferencesInFile(fileName, options, symbol, canInvalidateProject, userOpName) + node { + if fastCheck <> Some true then + return! backgroundCompiler.FindReferencesInFile(fileName, options, symbol, canInvalidateProject, userOpName) + else + let! parseResults = backgroundCompiler.GetBackgroundParseResultsForFileInProject(fileName, options, userOpName) + + if + parseResults.ParseTree.Identifiers |> Set.contains symbol.DisplayNameCore + || parseResults.ParseTree.Identifiers |> NamesContainAttribute symbol + then + return! backgroundCompiler.FindReferencesInFile(fileName, options, symbol, canInvalidateProject, userOpName) + else + return Seq.empty + } |> Async.AwaitNodeCode member _.GetBackgroundSemanticClassificationForFile(fileName: string, options: FSharpProjectOptions, ?userOpName) = diff --git a/src/Compiler/Service/service.fsi b/src/Compiler/Service/service.fsi index 31801bfbf46..697e6ae2cb8 100644 --- a/src/Compiler/Service/service.fsi +++ b/src/Compiler/Service/service.fsi @@ -300,12 +300,14 @@ type public FSharpChecker = /// The options for the project or script, used to determine active --define conditionals and other options relevant to parsing. /// The symbol to find all uses in the file. /// Default: true. If true, this call can invalidate the current state of project if the options have changed. If false, the current state of the project will be used. + /// Default: false. Experimental feature that makes the operation faster. /// An optional string used for tracing compiler operations associated with this request. member FindBackgroundReferencesInFile: fileName: string * options: FSharpProjectOptions * symbol: FSharpSymbol * ?canInvalidateProject: bool * + ?fastCheck: bool * ?userOpName: string -> Async diff --git a/src/Compiler/SyntaxTree/SyntaxTree.fs b/src/Compiler/SyntaxTree/SyntaxTree.fs index b74ab1d947d..30a864946a5 100644 --- a/src/Compiler/SyntaxTree/SyntaxTree.fs +++ b/src/Compiler/SyntaxTree/SyntaxTree.fs @@ -1680,7 +1680,8 @@ type ParsedImplFileInput = hashDirectives: ParsedHashDirective list * contents: SynModuleOrNamespace list * flags: (bool * bool) * - trivia: ParsedImplFileInputTrivia + trivia: ParsedImplFileInputTrivia * + identifiers: Set member x.QualifiedName = (let (ParsedImplFileInput (qualifiedNameOfFile = qualNameOfFile)) = x in qualNameOfFile) @@ -1712,7 +1713,8 @@ type ParsedSigFileInput = scopedPragmas: ScopedPragma list * hashDirectives: ParsedHashDirective list * contents: SynModuleOrNamespaceSig list * - trivia: ParsedSigFileInputTrivia + trivia: ParsedSigFileInputTrivia * + identifiers: Set member x.QualifiedName = (let (ParsedSigFileInput (qualifiedNameOfFile = qualNameOfFile)) = x in qualNameOfFile) @@ -1755,3 +1757,9 @@ type ParsedInput = | ParsedInput.ImplFile (ParsedImplFileInput(contents = SynModuleOrNamespace (range = m) :: _)) | ParsedInput.SigFile (ParsedSigFileInput(contents = SynModuleOrNamespaceSig (range = m) :: _)) -> m | _ -> rangeN inp.FileName 0 + + [] + member inp.Identifiers = + match inp with + | ParsedInput.ImplFile (ParsedImplFileInput (identifiers = identifiers)) + | ParsedInput.SigFile (ParsedSigFileInput (identifiers = identifiers)) -> identifiers diff --git a/src/Compiler/SyntaxTree/SyntaxTree.fsi b/src/Compiler/SyntaxTree/SyntaxTree.fsi index 43d37d47f1b..f878e5fa21f 100644 --- a/src/Compiler/SyntaxTree/SyntaxTree.fsi +++ b/src/Compiler/SyntaxTree/SyntaxTree.fsi @@ -1889,7 +1889,8 @@ type ParsedImplFileInput = hashDirectives: ParsedHashDirective list * contents: SynModuleOrNamespace list * flags: (bool * bool) * - trivia: ParsedImplFileInputTrivia + trivia: ParsedImplFileInputTrivia * + identifiers: Set member FileName: string @@ -1918,7 +1919,8 @@ type ParsedSigFileInput = scopedPragmas: ScopedPragma list * hashDirectives: ParsedHashDirective list * contents: SynModuleOrNamespaceSig list * - trivia: ParsedSigFileInputTrivia + trivia: ParsedSigFileInputTrivia * + identifiers: Set member FileName: string @@ -1952,3 +1954,6 @@ type ParsedInput = /// Gets the #nowarn and other scoped pragmas member ScopedPragmas: ScopedPragma list + + /// Gets a set of all identifiers used in this parsed input + member Identifiers: Set diff --git a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj index 10d0a1f6b50..91555d01068 100644 --- a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj +++ b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj @@ -202,6 +202,7 @@ + diff --git a/tests/FSharp.Compiler.ComponentTests/FSharpChecker/FindReferences.fs b/tests/FSharp.Compiler.ComponentTests/FSharpChecker/FindReferences.fs index 48cfdf9168a..b421de62027 100644 --- a/tests/FSharp.Compiler.ComponentTests/FSharpChecker/FindReferences.fs +++ b/tests/FSharp.Compiler.ComponentTests/FSharpChecker/FindReferences.fs @@ -1,7 +1,7 @@ module FSharp.Compiler.ComponentTests.FSharpChecker.FindReferences -open FSharp.Compiler.CodeAnalysis open Xunit +open FSharp.Compiler.CodeAnalysis open FSharp.Test.ProjectGeneration type Occurence = Definition | InType | Use @@ -53,7 +53,7 @@ let ``Finding usage of type via FindReference should also find it's constructors createProject().Workflow { placeCursor "First" 7 11 "type MyType() =" ["MyType"] - findAllReferences "First" (fun (ranges:list) -> + findAllReferencesInFile "First" (fun (ranges:list) -> let ranges = ranges |> List.sortBy (fun r -> r.StartLine) @@ -82,7 +82,7 @@ secondA.DoNothing(secondB) project.Workflow { placeCursor "First" 7 11 "type MyType() =" ["MyType"] - findAllReferences "Second" (fun (ranges:list) -> + findAllReferencesInFile "Second" (fun (ranges:list) -> let ranges = ranges |> List.sortBy (fun r -> r.StartLine) @@ -96,3 +96,97 @@ secondA.DoNothing(secondB) } +[] +let ``Finding references in project`` () = + let size = 20 + + let project = + { SyntheticProject.Create() with + SourceFiles = [ + sourceFile $"File%03d{0}" [] |> addSignatureFile + for i in 1..size do + sourceFile $"File%03d{i}" [$"File%03d{i-1}"] + ] + } + |> updateFile "File005" (addDependency "File000") + |> updateFile "File010" (addDependency "File000") + + let checker = FSharpChecker.Create(enableBackgroundItemKeyStoreAndSemanticClassification = true) + + project.WorkflowWith checker { + findAllReferencesToModuleFromFile "File000" true (expectNumberOfResults 5) + } + +[] +let ``We find back-ticked identifiers`` () = + SyntheticProject.Create( + { sourceFile "First" [] with ExtraSource = "let ``foo bar`` x = x + 5" }, + { sourceFile "Second" [] with ExtraSource = "let foo x = ModuleFirst.``foo bar`` x" }) + .Workflow { + placeCursor "Second" 6 35 "let foo x = ModuleFirst.``foo bar`` x" ["``foo bar``"] + findAllReferences (expectToFind [ + "FileFirst.fs", 6, 4, 15 + "FileSecond.fs", 6, 12, 35 + ]) + } + +[] +let ``We find operators`` () = + SyntheticProject.Create( + { sourceFile "First" [] with ExtraSource = "let (++) x y = x - y" }, + { sourceFile "Second" [] with ExtraSource = """ +open ModuleFirst +let foo x = x ++ 4""" }) + .Workflow { + placeCursor "Second" 8 16 "let foo x = x ++ 4" ["++"] + findAllReferences (expectToFind [ + "FileFirst.fs", 6, 5, 7 + "FileSecond.fs", 8, 14, 16 + ]) + } + +module Attributes = + + let project() = SyntheticProject.Create( + { sourceFile "First" [] with ExtraSource = "type MyAttribute() = inherit System.Attribute()" }, + { sourceFile "Second" [] with ExtraSource = """ +open ModuleFirst +[] +let foo x = 4""" }, + { sourceFile "Third" [] with ExtraSource = """ +open ModuleFirst +[] +let foo x = 5""" }) + + [] + let ``We find attributes from definition`` () = + project().Workflow { + placeCursor "First" 6 16 "type MyAttribute() = inherit System.Attribute()" ["MyAttribute"] + findAllReferences (expectToFind [ + "FileFirst.fs", 6, 5, 16 + "FileSecond.fs", 8, 2, 4 + "FileThird.fs", 8, 2, 13 + ]) + } + + [] + let ``We find attributes from usage`` () = + project().Workflow { + placeCursor "Second" 8 4 "[]" ["My"] + findAllReferences (expectToFind [ + "FileFirst.fs", 6, 5, 16 + "FileSecond.fs", 8, 2, 4 + "FileThird.fs", 8, 2, 13 + ]) + } + + [] + let ``We find attributes from usage with Attribute suffix`` () = + project().Workflow { + placeCursor "Third" 8 13 "[]" ["MyAttribute"] + findAllReferences (expectToFind [ + "FileFirst.fs", 6, 5, 16 + "FileSecond.fs", 8, 2, 4 + "FileThird.fs", 8, 2, 13 + ]) + } diff --git a/tests/FSharp.Compiler.ComponentTests/Miscellaneous/SemanticClassificationKeyBuilder.fs b/tests/FSharp.Compiler.ComponentTests/Miscellaneous/SemanticClassificationKeyBuilder.fs new file mode 100644 index 00000000000..d7a00d8a133 --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/Miscellaneous/SemanticClassificationKeyBuilder.fs @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. + +module FSharp.Compiler.ComponentTests.Miscellaneous.SemanticClassificationKeyStoreBuilder + +open Xunit +open FSharp.Compiler.EditorServices + +[] +let ``Build empty`` () = + let sckBuilder = SemanticClassificationKeyStoreBuilder() + sckBuilder.WriteAll [||] + + let res = sckBuilder.TryBuildAndReset() + Assert.Equal(None, res) diff --git a/tests/FSharp.Compiler.Service.Tests/FSharp.CompilerService.SurfaceArea.netstandard.expected b/tests/FSharp.Compiler.Service.Tests/FSharp.CompilerService.SurfaceArea.netstandard.expected index 4fbe51be8a1..65f6aa81b24 100644 --- a/tests/FSharp.Compiler.Service.Tests/FSharp.CompilerService.SurfaceArea.netstandard.expected +++ b/tests/FSharp.Compiler.Service.Tests/FSharp.CompilerService.SurfaceArea.netstandard.expected @@ -2026,7 +2026,7 @@ FSharp.Compiler.CodeAnalysis.FSharpChecker: Microsoft.FSharp.Control.FSharpAsync FSharp.Compiler.CodeAnalysis.FSharpChecker: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.CodeAnalysis.FSharpCheckFileAnswer]] CheckFileInProjectAllowingStaleCachedResults(FSharp.Compiler.CodeAnalysis.FSharpParseFileResults, System.String, Int32, System.String, FSharp.Compiler.CodeAnalysis.FSharpProjectOptions, Microsoft.FSharp.Core.FSharpOption`1[System.String]) FSharp.Compiler.CodeAnalysis.FSharpChecker: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.EditorServices.SemanticClassificationView]] GetBackgroundSemanticClassificationForFile(System.String, FSharp.Compiler.CodeAnalysis.FSharpProjectOptions, Microsoft.FSharp.Core.FSharpOption`1[System.String]) FSharp.Compiler.CodeAnalysis.FSharpChecker: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.Unit] NotifyProjectCleaned(FSharp.Compiler.CodeAnalysis.FSharpProjectOptions, Microsoft.FSharp.Core.FSharpOption`1[System.String]) -FSharp.Compiler.CodeAnalysis.FSharpChecker: Microsoft.FSharp.Control.FSharpAsync`1[System.Collections.Generic.IEnumerable`1[FSharp.Compiler.Text.Range]] FindBackgroundReferencesInFile(System.String, FSharp.Compiler.CodeAnalysis.FSharpProjectOptions, FSharp.Compiler.Symbols.FSharpSymbol, Microsoft.FSharp.Core.FSharpOption`1[System.Boolean], Microsoft.FSharp.Core.FSharpOption`1[System.String]) +FSharp.Compiler.CodeAnalysis.FSharpChecker: Microsoft.FSharp.Control.FSharpAsync`1[System.Collections.Generic.IEnumerable`1[FSharp.Compiler.Text.Range]] FindBackgroundReferencesInFile(System.String, FSharp.Compiler.CodeAnalysis.FSharpProjectOptions, FSharp.Compiler.Symbols.FSharpSymbol, Microsoft.FSharp.Core.FSharpOption`1[System.Boolean], Microsoft.FSharp.Core.FSharpOption`1[System.Boolean], Microsoft.FSharp.Core.FSharpOption`1[System.String]) FSharp.Compiler.CodeAnalysis.FSharpChecker: Microsoft.FSharp.Control.FSharpAsync`1[System.Tuple`2[FSharp.Compiler.CodeAnalysis.FSharpParseFileResults,FSharp.Compiler.CodeAnalysis.FSharpCheckFileAnswer]] ParseAndCheckFileInProject(System.String, Int32, FSharp.Compiler.Text.ISourceText, FSharp.Compiler.CodeAnalysis.FSharpProjectOptions, Microsoft.FSharp.Core.FSharpOption`1[System.String]) FSharp.Compiler.CodeAnalysis.FSharpChecker: Microsoft.FSharp.Control.FSharpAsync`1[System.Tuple`2[FSharp.Compiler.CodeAnalysis.FSharpParseFileResults,FSharp.Compiler.CodeAnalysis.FSharpCheckFileResults]] GetBackgroundCheckResultsForFileInProject(System.String, FSharp.Compiler.CodeAnalysis.FSharpProjectOptions, Microsoft.FSharp.Core.FSharpOption`1[System.String]) FSharp.Compiler.CodeAnalysis.FSharpChecker: Microsoft.FSharp.Control.FSharpAsync`1[System.Tuple`2[FSharp.Compiler.CodeAnalysis.FSharpProjectOptions,Microsoft.FSharp.Collections.FSharpList`1[FSharp.Compiler.Diagnostics.FSharpDiagnostic]]] GetProjectOptionsFromScript(System.String, FSharp.Compiler.Text.ISourceText, Microsoft.FSharp.Core.FSharpOption`1[System.Boolean], Microsoft.FSharp.Core.FSharpOption`1[System.DateTime], Microsoft.FSharp.Core.FSharpOption`1[System.String[]], Microsoft.FSharp.Core.FSharpOption`1[System.Boolean], Microsoft.FSharp.Core.FSharpOption`1[System.Boolean], Microsoft.FSharp.Core.FSharpOption`1[System.Boolean], Microsoft.FSharp.Core.FSharpOption`1[System.String], Microsoft.FSharp.Core.FSharpOption`1[System.Int64], Microsoft.FSharp.Core.FSharpOption`1[System.String]) @@ -5600,7 +5600,7 @@ FSharp.Compiler.Syntax.ParsedImplFileInput: Boolean get_IsLastCompiland() FSharp.Compiler.Syntax.ParsedImplFileInput: Boolean get_IsScript() FSharp.Compiler.Syntax.ParsedImplFileInput: Boolean get_isScript() FSharp.Compiler.Syntax.ParsedImplFileInput: Boolean isScript -FSharp.Compiler.Syntax.ParsedImplFileInput: FSharp.Compiler.Syntax.ParsedImplFileInput NewParsedImplFileInput(System.String, Boolean, FSharp.Compiler.Syntax.QualifiedNameOfFile, Microsoft.FSharp.Collections.FSharpList`1[FSharp.Compiler.Syntax.ScopedPragma], Microsoft.FSharp.Collections.FSharpList`1[FSharp.Compiler.Syntax.ParsedHashDirective], Microsoft.FSharp.Collections.FSharpList`1[FSharp.Compiler.Syntax.SynModuleOrNamespace], System.Tuple`2[System.Boolean,System.Boolean], FSharp.Compiler.SyntaxTrivia.ParsedImplFileInputTrivia) +FSharp.Compiler.Syntax.ParsedImplFileInput: FSharp.Compiler.Syntax.ParsedImplFileInput NewParsedImplFileInput(System.String, Boolean, FSharp.Compiler.Syntax.QualifiedNameOfFile, Microsoft.FSharp.Collections.FSharpList`1[FSharp.Compiler.Syntax.ScopedPragma], Microsoft.FSharp.Collections.FSharpList`1[FSharp.Compiler.Syntax.ParsedHashDirective], Microsoft.FSharp.Collections.FSharpList`1[FSharp.Compiler.Syntax.SynModuleOrNamespace], System.Tuple`2[System.Boolean,System.Boolean], FSharp.Compiler.SyntaxTrivia.ParsedImplFileInputTrivia, Microsoft.FSharp.Collections.FSharpSet`1[System.String]) FSharp.Compiler.Syntax.ParsedImplFileInput: FSharp.Compiler.Syntax.QualifiedNameOfFile QualifiedName FSharp.Compiler.Syntax.ParsedImplFileInput: FSharp.Compiler.Syntax.QualifiedNameOfFile get_QualifiedName() FSharp.Compiler.Syntax.ParsedImplFileInput: FSharp.Compiler.Syntax.QualifiedNameOfFile get_qualifiedNameOfFile() @@ -5623,6 +5623,8 @@ FSharp.Compiler.Syntax.ParsedImplFileInput: Microsoft.FSharp.Collections.FSharpL FSharp.Compiler.Syntax.ParsedImplFileInput: Microsoft.FSharp.Collections.FSharpList`1[FSharp.Compiler.Syntax.SynModuleOrNamespace] contents FSharp.Compiler.Syntax.ParsedImplFileInput: Microsoft.FSharp.Collections.FSharpList`1[FSharp.Compiler.Syntax.SynModuleOrNamespace] get_Contents() FSharp.Compiler.Syntax.ParsedImplFileInput: Microsoft.FSharp.Collections.FSharpList`1[FSharp.Compiler.Syntax.SynModuleOrNamespace] get_contents() +FSharp.Compiler.Syntax.ParsedImplFileInput: Microsoft.FSharp.Collections.FSharpSet`1[System.String] get_identifiers() +FSharp.Compiler.Syntax.ParsedImplFileInput: Microsoft.FSharp.Collections.FSharpSet`1[System.String] identifiers FSharp.Compiler.Syntax.ParsedImplFileInput: System.String FileName FSharp.Compiler.Syntax.ParsedImplFileInput: System.String ToString() FSharp.Compiler.Syntax.ParsedImplFileInput: System.String fileName @@ -5654,6 +5656,8 @@ FSharp.Compiler.Syntax.ParsedInput: Int32 Tag FSharp.Compiler.Syntax.ParsedInput: Int32 get_Tag() FSharp.Compiler.Syntax.ParsedInput: Microsoft.FSharp.Collections.FSharpList`1[FSharp.Compiler.Syntax.ScopedPragma] ScopedPragmas FSharp.Compiler.Syntax.ParsedInput: Microsoft.FSharp.Collections.FSharpList`1[FSharp.Compiler.Syntax.ScopedPragma] get_ScopedPragmas() +FSharp.Compiler.Syntax.ParsedInput: Microsoft.FSharp.Collections.FSharpSet`1[System.String] Identifiers +FSharp.Compiler.Syntax.ParsedInput: Microsoft.FSharp.Collections.FSharpSet`1[System.String] get_Identifiers() FSharp.Compiler.Syntax.ParsedInput: System.String FileName FSharp.Compiler.Syntax.ParsedInput: System.String ToString() FSharp.Compiler.Syntax.ParsedInput: System.String get_FileName() @@ -5718,7 +5722,7 @@ FSharp.Compiler.Syntax.ParsedSigFileFragment: Int32 Tag FSharp.Compiler.Syntax.ParsedSigFileFragment: Int32 get_Tag() FSharp.Compiler.Syntax.ParsedSigFileFragment: System.String ToString() FSharp.Compiler.Syntax.ParsedSigFileInput -FSharp.Compiler.Syntax.ParsedSigFileInput: FSharp.Compiler.Syntax.ParsedSigFileInput NewParsedSigFileInput(System.String, FSharp.Compiler.Syntax.QualifiedNameOfFile, Microsoft.FSharp.Collections.FSharpList`1[FSharp.Compiler.Syntax.ScopedPragma], Microsoft.FSharp.Collections.FSharpList`1[FSharp.Compiler.Syntax.ParsedHashDirective], Microsoft.FSharp.Collections.FSharpList`1[FSharp.Compiler.Syntax.SynModuleOrNamespaceSig], FSharp.Compiler.SyntaxTrivia.ParsedSigFileInputTrivia) +FSharp.Compiler.Syntax.ParsedSigFileInput: FSharp.Compiler.Syntax.ParsedSigFileInput NewParsedSigFileInput(System.String, FSharp.Compiler.Syntax.QualifiedNameOfFile, Microsoft.FSharp.Collections.FSharpList`1[FSharp.Compiler.Syntax.ScopedPragma], Microsoft.FSharp.Collections.FSharpList`1[FSharp.Compiler.Syntax.ParsedHashDirective], Microsoft.FSharp.Collections.FSharpList`1[FSharp.Compiler.Syntax.SynModuleOrNamespaceSig], FSharp.Compiler.SyntaxTrivia.ParsedSigFileInputTrivia, Microsoft.FSharp.Collections.FSharpSet`1[System.String]) FSharp.Compiler.Syntax.ParsedSigFileInput: FSharp.Compiler.Syntax.QualifiedNameOfFile QualifiedName FSharp.Compiler.Syntax.ParsedSigFileInput: FSharp.Compiler.Syntax.QualifiedNameOfFile get_QualifiedName() FSharp.Compiler.Syntax.ParsedSigFileInput: FSharp.Compiler.Syntax.QualifiedNameOfFile get_qualifiedNameOfFile() @@ -5741,6 +5745,8 @@ FSharp.Compiler.Syntax.ParsedSigFileInput: Microsoft.FSharp.Collections.FSharpLi FSharp.Compiler.Syntax.ParsedSigFileInput: Microsoft.FSharp.Collections.FSharpList`1[FSharp.Compiler.Syntax.SynModuleOrNamespaceSig] contents FSharp.Compiler.Syntax.ParsedSigFileInput: Microsoft.FSharp.Collections.FSharpList`1[FSharp.Compiler.Syntax.SynModuleOrNamespaceSig] get_Contents() FSharp.Compiler.Syntax.ParsedSigFileInput: Microsoft.FSharp.Collections.FSharpList`1[FSharp.Compiler.Syntax.SynModuleOrNamespaceSig] get_contents() +FSharp.Compiler.Syntax.ParsedSigFileInput: Microsoft.FSharp.Collections.FSharpSet`1[System.String] get_identifiers() +FSharp.Compiler.Syntax.ParsedSigFileInput: Microsoft.FSharp.Collections.FSharpSet`1[System.String] identifiers FSharp.Compiler.Syntax.ParsedSigFileInput: System.String FileName FSharp.Compiler.Syntax.ParsedSigFileInput: System.String ToString() FSharp.Compiler.Syntax.ParsedSigFileInput: System.String fileName diff --git a/tests/FSharp.Test.Utilities/ProjectGeneration.fs b/tests/FSharp.Test.Utilities/ProjectGeneration.fs index c565ca9b779..19de5910387 100644 --- a/tests/FSharp.Test.Utilities/ProjectGeneration.fs +++ b/tests/FSharp.Test.Utilities/ProjectGeneration.fs @@ -28,7 +28,10 @@ let private projectRoot = "test-projects" let private defaultFunctionName = "f" -type SignatureFile = No | AutoGenerated | Custom of string +type SignatureFile = + | No + | AutoGenerated + | Custom of string type SyntheticSourceFile = @@ -48,6 +51,8 @@ type SyntheticSourceFile = member this.FileName = $"File{this.Id}.fs" member this.SignatureFileName = $"{this.FileName}i" + member this.TypeName = $"T{this.Id}V_{this.PublicVersion}" + member this.ModuleName = $"Module{this.Id}" member this.HasSignatureFile = match this.SignatureFile with @@ -76,13 +81,12 @@ type SyntheticProject = static member Create(?name: string) = let name = defaultArg name $"TestProject_{Guid.NewGuid().ToString()[..7]}" let dir = Path.GetFullPath projectRoot - { - Name = name - ProjectDir = dir ++ name - SourceFiles = [] - DependsOn = [] - RecursiveNamespace = false - } + + { Name = name + ProjectDir = dir ++ name + SourceFiles = [] + DependsOn = [] + RecursiveNamespace = false } static member Create([] sourceFiles: SyntheticSourceFile[]) = { SyntheticProject.Create() with SourceFiles = sourceFiles |> List.ofArray } @@ -151,14 +155,14 @@ module Internal = seq { if project.RecursiveNamespace then $"namespace rec {project.Name}" - $"module Module{f.Id}" + $"module {f.ModuleName}" else - $"module %s{project.Name}.Module{f.Id}" + $"module %s{project.Name}.{f.ModuleName}" for p in project.DependsOn do $"open {p.Name}" - $"type T{f.Id}V_{f.PublicVersion}<'a> = T{f.Id} of 'a" + $"type {f.TypeName}<'a> = T{f.Id} of 'a" $"let {f.FunctionName} x =" @@ -274,7 +278,8 @@ module ProjectOperations = let addDependency fileId f : SyntheticSourceFile = { f with DependsOn = fileId :: f.DependsOn } - let addSignatureFile f = { f with SignatureFile = AutoGenerated } + let addSignatureFile f = + { f with SignatureFile = AutoGenerated } let checkFile fileId (project: SyntheticProject) (checker: FSharpChecker) = let file = project.Find fileId @@ -328,6 +333,18 @@ module ProjectOperations = expectOk result () Assert.Equal(oldSignature, newSignature) + let expectNumberOfResults expected (results: 'a list) = + if results.Length <> expected then + failwith $"Found {results.Length} references but expected to find {expected}" + + let expectToFind expected (foundRanges: range seq) = + let actual = + foundRanges + |> Seq.map (fun r -> Path.GetFileName(r.FileName), r.StartLine, r.StartColumn, r.EndColumn) + |> Seq.sortBy (fun (file, _, _, _) -> file) + |> Seq.toArray + Assert.Equal<(string * int * int * int)[]>(expected |> Seq.toArray, actual) + let rec saveProject (p: SyntheticProject) generateSignatureFiles checker = async { Directory.CreateDirectory(p.ProjectDir) |> ignore @@ -347,17 +364,43 @@ module ProjectOperations = let! results = checkFile file.Id project checker let signature = getSignature results writeFileIfChanged signatureFileName signature - | Custom signature -> - writeFileIfChanged signatureFileName signature + | Custom signature -> writeFileIfChanged signatureFileName signature | _ -> () writeFileIfChanged (p.ProjectDir ++ $"{p.Name}.fsproj") (renderFsProj p) } + type WorkflowContext = { Project: SyntheticProject Signatures: Map - Cursor : FSharp.Compiler.CodeAnalysis.FSharpSymbolUse option } + Cursor: FSharpSymbolUse option } + +let SaveAndCheckProject project checker = + async { + validateFileIdsAreUnique project + + do! saveProject project true checker + + let! results = checker.ParseAndCheckProject(project.GetProjectOptions checker) + + if not (Array.isEmpty results.Diagnostics) then + failwith $"Project {project.Name} failed initial check: \n%A{results.Diagnostics}" + + let! signatures = + Async.Sequential + [ for file in project.SourceFiles do + async { + let! result = checkFile file.Id project checker + let signature = getSignature result + return file.Id, signature + } ] + + return + { Project = project + Signatures = Map signatures + Cursor = None } + } type ProjectWorkflowBuilder(initialProject: SyntheticProject, ?checker: FSharpChecker) = @@ -379,37 +422,17 @@ type ProjectWorkflowBuilder(initialProject: SyntheticProject, ?checker: FSharpCh member this.Checker = checker member this.Yield _ = - async { - validateFileIdsAreUnique initialProject - - do! saveProject initialProject true checker - - let! results = checker.ParseAndCheckProject(initialProject.GetProjectOptions checker) + SaveAndCheckProject initialProject checker - if not (Array.isEmpty results.Diagnostics) then - failwith $"Project {initialProject.Name} failed initial check: \n%A{results.Diagnostics}" - - let! signatures = - Async.Sequential - [ for file in initialProject.SourceFiles do - async { - let! result = checkFile file.Id initialProject checker - let signature = getSignature result - return file.Id, signature - } ] - - return - { Project = initialProject - Signatures = Map signatures - Cursor = None } - } + member this.DeleteProjectDir() = + if Directory.Exists initialProject.ProjectDir then + Directory.Delete(initialProject.ProjectDir, true) member this.Run(workflow: Async) = try Async.RunSynchronously workflow finally - if Directory.Exists initialProject.ProjectDir then - Directory.Delete(initialProject.ProjectDir, true) + this.DeleteProjectDir() /// Change contents of given file using `processFile` function. /// Does not save the file to disk. @@ -451,51 +474,79 @@ type ProjectWorkflowBuilder(initialProject: SyntheticProject, ?checker: FSharpCh return { ctx with Signatures = ctx.Signatures.Add(fileId, newSignature) } } - /// Find a symbol using the provided range, mimicing placing a cursor on it in IDE scenarios - [] - member this.PlaceCursor(workflow: Async, fileId, line, colAtEndOfNames, fullLine, symbolNames) = + member this.CheckFile(workflow: Async, fileId: string, processResults) = async { let! ctx = workflow let! results = checkFile fileId ctx.Project checker let typeCheckResults = getTypeCheckResult results - let su = typeCheckResults.GetSymbolUseAtLocation(line,colAtEndOfNames,fullLine,symbolNames) + let newSignature = getSignature results + + processResults typeCheckResults - return {ctx with Cursor = su} + return { ctx with Signatures = ctx.Signatures.Add(fileId, newSignature) } } + /// Find a symbol using the provided range, mimicking placing a cursor on it in IDE scenarios + [] + member this.PlaceCursor(workflow: Async, fileId, line, colAtEndOfNames, fullLine, symbolNames) = + async { + let! ctx = workflow + let! results = checkFile fileId ctx.Project checker + let typeCheckResults = getTypeCheckResult results + + let su = + typeCheckResults.GetSymbolUseAtLocation(line, colAtEndOfNames, fullLine, symbolNames) + if su.IsNone then + let file = ctx.Project.Find fileId + failwith $"No symbol found in {file.FileName} at {line}:{colAtEndOfNames}\nFile contents:\n\n{renderSourceFile ctx.Project file}\n" + + return { ctx with Cursor = su } + } /// Find all references within a single file, results are provided to the 'processResults' function - [] - member this.FindAllReferences(workflow: Async, fileId: string, processResults) = - async{ + [] + member this.FindAllReferencesInFile(workflow: Async, fileId: string, processResults) = + async { let! ctx = workflow - let po = ctx.Project.GetProjectOptions checker - let s = ctx.Cursor |> Option.defaultWith (fun () -> failwith $"Please place cursor at a valid location via {nameof(this.PlaceCursor)} first") - let file = ctx.Project.Find fileId + let options = ctx.Project.GetProjectOptions checker + + let symbolUse = + ctx.Cursor + |> Option.defaultWith (fun () -> + failwith $"Please place cursor at a valid location via {nameof this.PlaceCursor} first") + + let file = ctx.Project.Find fileId let absFileName = ctx.Project.ProjectDir ++ file.FileName - let! results = checker.FindBackgroundReferencesInFile(absFileName,po, s.Symbol) + let! results = checker.FindBackgroundReferencesInFile(absFileName, options, symbolUse.Symbol, fastCheck = true) processResults (results |> Seq.toList) return ctx } - /// Parse and type check given file and process the results using `processResults` function. - [] - member this.CheckFile(workflow: Async, fileId: string, processResults) = + /// Find all references within the project, results are provided to the 'processResults' function + [] + member this.FindAllReferences(workflow: Async, processResults) = async { let! ctx = workflow - let! results = checkFile fileId ctx.Project checker - let typeCheckResults = getTypeCheckResult results + let options = ctx.Project.GetProjectOptions checker - let newSignature = getSignature results + let symbolUse = + ctx.Cursor + |> Option.defaultWith (fun () -> + failwith $"Please place cursor at a valid location via {nameof this.PlaceCursor} first") - processResults typeCheckResults + let! results = + [ for f in options.SourceFiles do + checker.FindBackgroundReferencesInFile(f, options, symbolUse.Symbol, fastCheck = true) ] + |> Async.Parallel - return { ctx with Signatures = ctx.Signatures.Add(fileId, newSignature) } + results |> Seq.collect id |> Seq.toList |> processResults + return ctx } + /// Save given file to disk. [] member this.SaveFile(workflow: Async, fileId: string) = @@ -515,12 +566,65 @@ type ProjectWorkflowBuilder(initialProject: SyntheticProject, ?checker: FSharpCh return ctx } + /// Find all references to a module defined in a given file. + /// These should only be found in files that depend on this file. + /// + /// Requires `enableBackgroundItemKeyStoreAndSemanticClassification` to be true in the checker. + [] + member this.FindAllReferencesToModuleFromFile(workflow, fileId, fastCheck, processResults) = + async { + let! ctx = workflow + let! results = checkFile fileId ctx.Project checker + let typeCheckResult = getTypeCheckResult results + let moduleName = (ctx.Project.Find fileId).ModuleName + + let symbolUse = + typeCheckResult.GetSymbolUseAtLocation( + 1, + moduleName.Length + ctx.Project.Name.Length + 8, + $"module {ctx.Project.Name}.{moduleName}", + [ moduleName ] + ) + |> Option.defaultWith (fun () -> failwith "no symbol use found") + + let options = ctx.Project.GetProjectOptions checker + + let! results = + [ for f in options.SourceFiles do + checker.FindBackgroundReferencesInFile(f, options, symbolUse.Symbol, fastCheck = fastCheck) ] + |> Async.Parallel + + results |> Seq.collect id |> Seq.toList |> processResults + return ctx + } + /// Execute a set of operations on a given synthetic project. /// The project is saved to disk and type checked at the start. let projectWorkflow project = ProjectWorkflowBuilder project +/// Just like ProjectWorkflowBuilder but expects a saved and checked project +/// so time is not spent on it during the benchmark. +/// Also does not delete the project at the end of a run - so it keeps working for the next iteration +type ProjectBenchmarkBuilder(initialContext: WorkflowContext, checker) = + inherit ProjectWorkflowBuilder(initialContext.Project, checker) + + static member Create(initialProject, ?checker) = + async { + let checker = defaultArg checker (FSharpChecker.Create()) + let! initialContext = SaveAndCheckProject initialProject checker + return ProjectBenchmarkBuilder(initialContext, checker) + } + + member this.Yield _ = async.Return initialContext + + member this.Run(workflow: Async) = Async.RunSynchronously workflow + + type SyntheticProject with + /// Execute a set of operations on this project. /// The project is saved to disk and type checked at the start. member this.Workflow = projectWorkflow this + + member this.WorkflowWith checker = ProjectWorkflowBuilder(this, checker) diff --git a/tests/benchmarks/FCSBenchmarks/CompilerServiceBenchmarks/BackgroundCompilerBenchmarks.fs b/tests/benchmarks/FCSBenchmarks/CompilerServiceBenchmarks/BackgroundCompilerBenchmarks.fs new file mode 100644 index 00000000000..3062f194353 --- /dev/null +++ b/tests/benchmarks/FCSBenchmarks/CompilerServiceBenchmarks/BackgroundCompilerBenchmarks.fs @@ -0,0 +1,101 @@ +module FSharp.Benchmarks.BackgroundCompilerBenchmarks + + +open System.IO +open BenchmarkDotNet.Attributes +open FSharp.Test.ProjectGeneration +open FSharp.Compiler.CodeAnalysis + + +[] +let FSharpCategory = "fsharp" + + +[] +[] +type BackgroundCompilerBenchmarks () = + + let size = 50 + + let somethingToCompile = File.ReadAllText (__SOURCE_DIRECTORY__ ++ "SomethingToCompile.fs") + + [] + member val FastFindReferences = true with get,set + + [] + member val EmptyCache = true with get,set + + member val Benchmark = Unchecked.defaultof with get, set + + member this.setup(project) = + let checker = FSharpChecker.Create( + enableBackgroundItemKeyStoreAndSemanticClassification = true + ) + this.Benchmark <- ProjectBenchmarkBuilder.Create(project, checker) |> Async.RunSynchronously + + [] + member this.EditFirstFile_OnlyInternalChange() = + if this.EmptyCache then + this.Benchmark.Checker.InvalidateAll() + this.Benchmark.Checker.ClearLanguageServiceRootCachesAndCollectAndFinalizeAllTransients() + + /// Only file at the top of the list has the reference + [] + member this.FindAllReferences_BestCase_Setup() = + this.setup + { SyntheticProject.Create() with + SourceFiles = [ + sourceFile $"File%03d{0}" [] |> addSignatureFile + for i in 1..size do + { sourceFile $"File%03d{i}" [$"File%03d{i-1}"] with ExtraSource = somethingToCompile } + ] + } + + [] + member this.FindAllReferences_BestCase() = + this.Benchmark { + findAllReferencesToModuleFromFile "File000" this.FastFindReferences (expectNumberOfResults 3) + } + + /// Few files in the middle have the reference + [] + member this.FindAllReferences_MediumCase_Setup() = + this.setup( + { SyntheticProject.Create() with + SourceFiles = [ + sourceFile $"File%03d{0}" [] |> addSignatureFile + for i in 1..size do + { sourceFile $"File%03d{i}" [$"File%03d{i-1}"] with ExtraSource = somethingToCompile } + ] + } + |> updateFile $"File%03d{size / 2 - 1}" (addDependency "File000") + |> updateFile $"File%03d{size / 2 }" (addDependency "File000") + |> updateFile $"File%03d{size / 2 + 1}" (addDependency "File000")) + + [] + member this.FindAllReferences_MediumCase() = + this.Benchmark { + findAllReferencesToModuleFromFile "File000" this.FastFindReferences (expectNumberOfResults 6) + } + + /// All files have the reference, have to check everything + [] + member this.FindAllReferences_WorstCase_Setup() = + this.setup + { SyntheticProject.Create() with + SourceFiles = [ + sourceFile $"File%03d{0}" [] |> addSignatureFile + for i in 1..size do + { sourceFile $"File%03d{i}" [$"File000"] with ExtraSource = somethingToCompile } + ] + } + + [] + member this.FindAllReferences_WorstCase() = + this.Benchmark { + findAllReferencesToModuleFromFile "File000" this.FastFindReferences (expectNumberOfResults (size + 2)) + } + + [] + member this.Cleanup() = + this.Benchmark.DeleteProjectDir() diff --git a/tests/benchmarks/FCSBenchmarks/CompilerServiceBenchmarks/FSharp.Compiler.Benchmarks.fsproj b/tests/benchmarks/FCSBenchmarks/CompilerServiceBenchmarks/FSharp.Compiler.Benchmarks.fsproj index 0d5fe5df518..cc18675151e 100644 --- a/tests/benchmarks/FCSBenchmarks/CompilerServiceBenchmarks/FSharp.Compiler.Benchmarks.fsproj +++ b/tests/benchmarks/FCSBenchmarks/CompilerServiceBenchmarks/FSharp.Compiler.Benchmarks.fsproj @@ -15,6 +15,7 @@ + @@ -29,6 +30,7 @@ + diff --git a/tests/benchmarks/FCSBenchmarks/CompilerServiceBenchmarks/Program.fs b/tests/benchmarks/FCSBenchmarks/CompilerServiceBenchmarks/Program.fs index fa0474dbf5e..9aa05c3558c 100644 --- a/tests/benchmarks/FCSBenchmarks/CompilerServiceBenchmarks/Program.fs +++ b/tests/benchmarks/FCSBenchmarks/CompilerServiceBenchmarks/Program.fs @@ -1,5 +1,6 @@ open BenchmarkDotNet.Running open FSharp.Compiler.Benchmarks +open BenchmarkDotNet.Configs [] let main args = diff --git a/tests/benchmarks/FCSBenchmarks/CompilerServiceBenchmarks/SomethingToCompile.fs b/tests/benchmarks/FCSBenchmarks/CompilerServiceBenchmarks/SomethingToCompile.fs new file mode 100644 index 00000000000..ad32fba5951 --- /dev/null +++ b/tests/benchmarks/FCSBenchmarks/CompilerServiceBenchmarks/SomethingToCompile.fs @@ -0,0 +1,1411 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. + +// Taken from Utilities/illib.fs + + +open System +open System.Collections.Generic +open System.Collections.Concurrent +open System.Diagnostics +open System.IO +open System.Threading +open System.Threading.Tasks +open System.Runtime.CompilerServices + + +[] +module internal PervasiveAutoOpens = + /// Logical shift right treating int32 as unsigned integer. + /// Code that uses this should probably be adjusted to use unsigned integer types. + let (>>>&) (x: int32) (n: int32) = int32 (uint32 x >>> n) + + let notlazy v = Lazy<_>.CreateFromValue v + + let inline isNil l = List.isEmpty l + + /// Returns true if the list has less than 2 elements. Otherwise false. + let inline isNilOrSingleton l = + match l with + | [] + | [ _ ] -> true + | _ -> false + + /// Returns true if the list contains exactly 1 element. Otherwise false. + let inline isSingleton l = + match l with + | [ _ ] -> true + | _ -> false + + type 'T MaybeNull when 'T: null and 'T: not struct = 'T + + let inline isNotNull (x: 'T) = not (isNull x) + + let inline (|NonNullQuick|) (x: 'T MaybeNull) = + match x with + | null -> raise (NullReferenceException()) + | v -> v + + let inline nonNull (x: 'T MaybeNull) = + match x with + | null -> raise (NullReferenceException()) + | v -> v + + let inline (|Null|NonNull|) (x: 'T MaybeNull) : Choice = + match x with + | null -> Null + | v -> NonNull v + + let inline nullArgCheck paramName (x: 'T MaybeNull) = + match x with + | null -> raise (ArgumentNullException(paramName)) + | v -> v + + let inline (===) x y = LanguagePrimitives.PhysicalEquality x y + + /// Per the docs the threshold for the Large Object Heap is 85000 bytes: https://docs.microsoft.com/en-us/dotnet/standard/garbage-collection/large-object-heap#how-an-object-ends-up-on-the-large-object-heap-and-how-gc-handles-them + /// We set the limit to be 80k to account for larger pointer sizes for when F# is running 64-bit. + let LOH_SIZE_THRESHOLD_BYTES = 80_000 + + type String with + + member inline x.StartsWithOrdinal value = + x.StartsWith(value, StringComparison.Ordinal) + + member inline x.EndsWithOrdinal value = + x.EndsWith(value, StringComparison.Ordinal) + + member inline x.EndsWithOrdinalIgnoreCase value = + x.EndsWith(value, StringComparison.OrdinalIgnoreCase) + + /// Get an initialization hole + let getHole (r: _ ref) = + match r.Value with + | None -> failwith "getHole" + | Some x -> x + + let reportTime = + let mutable tFirst = None + let mutable tPrev = None + + fun showTimes descr -> + if showTimes then + let t = Process.GetCurrentProcess().UserProcessorTime.TotalSeconds + + let prev = + match tPrev with + | None -> 0.0 + | Some t -> t + + let first = + match tFirst with + | None -> + (tFirst <- Some t + t) + | Some t -> t + + printf " ilwrite: Cpu %4.1f (total) %4.1f (delta) - %s\n" (t - first) (t - prev) descr + tPrev <- Some t + + let foldOn p f z x = f z (p x) + + let notFound () = raise (KeyNotFoundException()) + + type Async with + + static member RunImmediate(computation: Async<'T>, ?cancellationToken) = + let cancellationToken = defaultArg cancellationToken Async.DefaultCancellationToken + let ts = TaskCompletionSource<'T>() + let task = ts.Task + + Async.StartWithContinuations( + computation, + (fun k -> ts.SetResult k), + (fun exn -> ts.SetException exn), + (fun _ -> ts.SetCanceled()), + cancellationToken + ) + + task.Result + +/// An efficient lazy for inline storage in a class type. Results in fewer thunks. +[] +type InlineDelayInit<'T when 'T: not struct> = + new(f: unit -> 'T) = + { + store = Unchecked.defaultof<'T> + func = Func<_>(f) + } + + val mutable store: 'T + val mutable func: Func<'T> + + member x.Value = + match x.func with + | null -> x.store + | _ -> + let res = LazyInitializer.EnsureInitialized(&x.store, x.func) + x.func <- Unchecked.defaultof<_> + res + +//------------------------------------------------------------------------- +// Library: projections +//------------------------------------------------------------------------ + +module Order = + let orderBy (p: 'T -> 'U) = + { new IComparer<'T> with + member _.Compare(x, xx) = compare (p x) (p xx) + } + + let orderOn p (pxOrder: IComparer<'U>) = + { new IComparer<'T> with + member _.Compare(x, xx) = pxOrder.Compare(p x, p xx) + } + + let toFunction (pxOrder: IComparer<'U>) x y = pxOrder.Compare(x, y) + +//------------------------------------------------------------------------- +// Library: arrays, lists, options, resizearrays +//------------------------------------------------------------------------- + +module Array = + + let mapq f inp = + match inp with + | [||] -> inp + | _ -> + let res = Array.map f inp + let len = inp.Length + let mutable eq = true + let mutable i = 0 + + while eq && i < len do + if not (inp[i] === res[i]) then + eq <- false + + i <- i + 1 + + if eq then inp else res + + let lengthsEqAndForall2 p l1 l2 = + Array.length l1 = Array.length l2 && Array.forall2 p l1 l2 + + let order (eltOrder: IComparer<'T>) = + { new IComparer> with + member _.Compare(xs, ys) = + let c = compare xs.Length ys.Length + + if c <> 0 then + c + else + let rec loop i = + if i >= xs.Length then + 0 + else + let c = eltOrder.Compare(xs[i], ys[i]) + if c <> 0 then c else loop (i + 1) + + loop 0 + } + + let existsOne p l = + let rec forallFrom p l n = + (n >= Array.length l) || (p l[n] && forallFrom p l (n + 1)) + + let rec loop p l n = + (n < Array.length l) + && (if p l[n] then + forallFrom (fun x -> not (p x)) l (n + 1) + else + loop p l (n + 1)) + + loop p l 0 + + let existsTrue (arr: bool[]) = + let rec loop n = + (n < arr.Length) && (arr[n] || loop (n + 1)) + + loop 0 + + let findFirstIndexWhereTrue (arr: _[]) p = + let rec look lo hi = + assert ((lo >= 0) && (hi >= 0)) + assert ((lo <= arr.Length) && (hi <= arr.Length)) + + if lo = hi then + lo + else + let i = (lo + hi) / 2 + + if p arr[i] then + if i = 0 then i + else if p arr[i - 1] then look lo i + else i + else + // not true here, look after + look (i + 1) hi + + look 0 arr.Length + + /// pass an array byref to reverse it in place + let revInPlace (array: 'T[]) = + if Array.isEmpty array then + () + else + let arrLen, revLen = array.Length - 1, array.Length / 2 - 1 + + for idx in 0..revLen do + let t1 = array[idx] + let t2 = array[arrLen - idx] + array[idx] <- t2 + array[arrLen - idx] <- t1 + + /// Async implementation of Array.map. + let mapAsync (mapping: 'T -> Async<'U>) (array: 'T[]) : Async<'U[]> = + let len = Array.length array + let result = Array.zeroCreate len + + async { // Apply the mapping function to each array element. + for i in 0 .. len - 1 do + let! mappedValue = mapping array[i] + result[i] <- mappedValue + + // Return the completed results. + return result + } + + /// Returns a new array with an element replaced with a given value. + let replace index value (array: _[]) = + if index >= array.Length then + raise (IndexOutOfRangeException "index") + + let res = Array.copy array + res[index] <- value + res + + /// Optimized arrays equality. ~100x faster than `array1 = array2` on strings. + /// ~2x faster for floats + /// ~0.8x slower for ints + let inline areEqual (xs: 'T[]) (ys: 'T[]) = + match xs, ys with + | null, null -> true + | [||], [||] -> true + | null, _ + | _, null -> false + | _ when xs.Length <> ys.Length -> false + | _ -> + let mutable break' = false + let mutable i = 0 + let mutable result = true + + while i < xs.Length && not break' do + if xs[i] <> ys[i] then + break' <- true + result <- false + + i <- i + 1 + + result + + /// Returns all heads of a given array. + /// For [|1;2;3|] it returns [|[|1; 2; 3|]; [|1; 2|]; [|1|]|] + let heads (array: 'T[]) = + let res = Array.zeroCreate<'T[]> array.Length + + for i = array.Length - 1 downto 0 do + res[i] <- array[0..i] + + res + + /// check if subArray is found in the wholeArray starting + /// at the provided index + let inline isSubArray (subArray: 'T[]) (wholeArray: 'T[]) index = + if subArray.Length = 0 then + true + elif subArray.Length > wholeArray.Length then + false + elif subArray.Length = wholeArray.Length then + areEqual subArray wholeArray + else + let rec loop subidx idx = + if subidx = subArray.Length then + true + elif subArray[subidx] = wholeArray[idx] then + loop (subidx + 1) (idx + 1) + else + false + + loop 0 index + + /// Returns true if one array has another as its subset from index 0. + let startsWith (prefix: _[]) (whole: _[]) = isSubArray prefix whole 0 + + /// Returns true if one array has trailing elements equal to another's. + let endsWith (suffix: _[]) (whole: _[]) = + isSubArray suffix whole (whole.Length - suffix.Length) + +module Option = + + let mapFold f s opt = + match opt with + | None -> None, s + | Some x -> + let x2, s2 = f s x + Some x2, s2 + + let attempt (f: unit -> 'T) = + try + Some(f ()) + with _ -> + None + +module List = + + let sortWithOrder (c: IComparer<'T>) elements = + List.sortWith (Order.toFunction c) elements + + let splitAfter n l = + let rec split_after_acc n l1 l2 = + if n <= 0 then + List.rev l1, l2 + else + split_after_acc (n - 1) ((List.head l2) :: l1) (List.tail l2) + + split_after_acc n [] l + + let existsi f xs = + let rec loop i xs = + match xs with + | [] -> false + | h :: t -> f i h || loop (i + 1) t + + loop 0 xs + + let lengthsEqAndForall2 p l1 l2 = + List.length l1 = List.length l2 && List.forall2 p l1 l2 + + let rec findi n f l = + match l with + | [] -> None + | h :: t -> if f h then Some(h, n) else findi (n + 1) f t + + let splitChoose select l = + let rec ch acc1 acc2 l = + match l with + | [] -> List.rev acc1, List.rev acc2 + | x :: xs -> + match select x with + | Choice1Of2 sx -> ch (sx :: acc1) acc2 xs + | Choice2Of2 sx -> ch acc1 (sx :: acc2) xs + + ch [] [] l + + let rec checkq l1 l2 = + match l1, l2 with + | h1 :: t1, h2 :: t2 -> h1 === h2 && checkq t1 t2 + | _ -> true + + let mapq (f: 'T -> 'T) inp = + assert not typeof<'T>.IsValueType + + match inp with + | [] -> inp + | [ h1a ] -> + let h2a = f h1a + if h1a === h2a then inp else [ h2a ] + | [ h1a; h1b ] -> + let h2a = f h1a + let h2b = f h1b + + if h1a === h2a && h1b === h2b then inp else [ h2a; h2b ] + | [ h1a; h1b; h1c ] -> + let h2a = f h1a + let h2b = f h1b + let h2c = f h1c + + if h1a === h2a && h1b === h2b && h1c === h2c then + inp + else + [ h2a; h2b; h2c ] + | _ -> + let res = List.map f inp + if checkq inp res then inp else res + + let frontAndBack l = + let rec loop acc l = + match l with + | [] -> + Debug.Assert(false, "empty list") + invalidArg "l" "empty list" + | [ h ] -> List.rev acc, h + | h :: t -> loop (h :: acc) t + + loop [] l + + let tryFrontAndBack l = + match l with + | [] -> None + | _ -> Some(frontAndBack l) + + let tryRemove f inp = + let rec loop acc l = + match l with + | [] -> None + | h :: t -> if f h then Some(h, List.rev acc @ t) else loop (h :: acc) t + + loop [] inp + + let zip4 l1 l2 l3 l4 = + List.zip l1 (List.zip3 l2 l3 l4) + |> List.map (fun (x1, (x2, x3, x4)) -> (x1, x2, x3, x4)) + + let unzip4 l = + let a, b, cd = List.unzip3 (List.map (fun (x, y, z, w) -> (x, y, (z, w))) l) + let c, d = List.unzip cd + a, b, c, d + + let rec iter3 f l1 l2 l3 = + match l1, l2, l3 with + | h1 :: t1, h2 :: t2, h3 :: t3 -> + f h1 h2 h3 + iter3 f t1 t2 t3 + | [], [], [] -> () + | _ -> failwith "iter3" + + let takeUntil p l = + let rec loop acc l = + match l with + | [] -> List.rev acc, [] + | x :: xs -> if p x then List.rev acc, l else loop (x :: acc) xs + + loop [] l + + let order (eltOrder: IComparer<'T>) = + { new IComparer<'T list> with + member _.Compare(xs, ys) = + let rec loop xs ys = + match xs, ys with + | [], [] -> 0 + | [], _ -> -1 + | _, [] -> 1 + | x :: xs, y :: ys -> + let cxy = eltOrder.Compare(x, y) + if cxy = 0 then loop xs ys else cxy + + loop xs ys + } + + let indexNotFound () = + raise (KeyNotFoundException("An index satisfying the predicate was not found in the collection")) + + let rec assoc x l = + match l with + | [] -> indexNotFound () + | (h, r) :: t -> if x = h then r else assoc x t + + let rec memAssoc x l = + match l with + | [] -> false + | (h, _) :: t -> x = h || memAssoc x t + + let rec memq x l = + match l with + | [] -> false + | h :: t -> LanguagePrimitives.PhysicalEquality x h || memq x t + + let mapNth n f xs = + let rec mn i = + function + | [] -> [] + | x :: xs -> if i = n then f x :: xs else x :: mn (i + 1) xs + + mn 0 xs + + let count pred xs = + List.fold (fun n x -> if pred x then n + 1 else n) 0 xs + + let headAndTail l = + match l with + | [] -> failwith "headAndTail" + | h :: t -> (h, t) + + // WARNING: not tail-recursive + let mapHeadTail fhead ftail = + function + | [] -> [] + | [ x ] -> [ fhead x ] + | x :: xs -> fhead x :: List.map ftail xs + + let collectFold f s l = + let l, s = List.mapFold f s l + List.concat l, s + + let collect2 f xs ys = List.concat (List.map2 f xs ys) + + let toArraySquared xss = + xss |> List.map List.toArray |> List.toArray + + let iterSquared f xss = xss |> List.iter (List.iter f) + + let collectSquared f xss = xss |> List.collect (List.collect f) + + let mapSquared f xss = xss |> List.map (List.map f) + + let mapFoldSquared f z xss = List.mapFold (List.mapFold f) z xss + + let forallSquared f xss = xss |> List.forall (List.forall f) + + let mapiSquared f xss = + xss |> List.mapi (fun i xs -> xs |> List.mapi (fun j x -> f i j x)) + + let existsSquared f xss = + xss |> List.exists (fun xs -> xs |> List.exists (fun x -> f x)) + + let mapiFoldSquared f z xss = + mapFoldSquared f z (xss |> mapiSquared (fun i j x -> (i, j, x))) + + let duplicates (xs: 'T list) = + xs + |> List.groupBy id + |> List.filter (fun (_, elems) -> Seq.length elems > 1) + |> List.map fst + + let internal allEqual (xs: 'T list) = + match xs with + | [] -> true + | h :: t -> t |> List.forall (fun h2 -> h = h2) + + let isSingleton xs = + match xs with + | [ _ ] -> true + | _ -> false + +module ResizeArray = + + /// Split a ResizeArray into an array of smaller chunks. + /// This requires `items/chunkSize` Array copies of length `chunkSize` if `items/chunkSize % 0 = 0`, + /// otherwise `items/chunkSize + 1` Array copies. + let chunkBySize chunkSize f (items: ResizeArray<'t>) = + // we could use Seq.chunkBySize here, but that would involve many enumerator.MoveNext() calls that we can sidestep with a bit of math + let itemCount = items.Count + + if itemCount = 0 then + [||] + else + let chunksCount = + match itemCount / chunkSize with + | n when itemCount % chunkSize = 0 -> n + | n -> n + 1 // any remainder means we need an additional chunk to store it + + [| + for index in 0 .. chunksCount - 1 do + let startIndex = index * chunkSize + let takeCount = min (itemCount - startIndex) chunkSize + + let holder = Array.zeroCreate takeCount + // we take a bounds-check hit here on each access. + // other alternatives here include + // * iterating across an IEnumerator (incurs MoveNext penalty) + // * doing a block copy using `List.CopyTo(index, array, index, count)` (requires more copies to do the mapping) + // none are significantly better. + for i in 0 .. takeCount - 1 do + holder[i] <- f items[startIndex + i] + + yield holder + |] + + /// Split a large ResizeArray into a series of array chunks that are each under the Large Object Heap limit. + /// This is done to help prevent a stop-the-world collection of the single large array, instead allowing for a greater + /// probability of smaller collections. Stop-the-world is still possible, just less likely. + let mapToSmallArrayChunks f (inp: ResizeArray<'t>) = + let itemSizeBytes = sizeof<'t> + // rounding down here is good because it ensures we don't go over + let maxArrayItemCount = LOH_SIZE_THRESHOLD_BYTES / itemSizeBytes + + // chunk the provided input into arrays that are smaller than the LOH limit + // in order to prevent long-term storage of those values + chunkBySize maxArrayItemCount f inp + +module ValueOptionInternal = + + let inline ofOption x = + match x with + | Some x -> ValueSome x + | None -> ValueNone + + let inline bind f x = + match x with + | ValueSome x -> f x + | ValueNone -> ValueNone + +module String = + let make (n: int) (c: char) : string = String(c, n) + + let get (str: string) i = str[i] + + let sub (s: string) (start: int) (len: int) = s.Substring(start, len) + + let contains (s: string) (c: char) = s.IndexOf c <> -1 + + let order = LanguagePrimitives.FastGenericComparer + + let lowercase (s: string) = s.ToLowerInvariant() + + let uppercase (s: string) = s.ToUpperInvariant() + + // Scripts that distinguish between upper and lower case (bicameral) DU Discriminators and Active Pattern identifiers are required to start with an upper case character. + // For valid identifiers where the case of the identifier can not be determined because there is no upper and lower case we will allow DU Discriminators and upper case characters + // to be used. This means that developers using unicameral scripts such as hindi, are not required to prefix these identifiers with an Upper case latin character. + // + let isLeadingIdentifierCharacterUpperCase (s: string) = + let isUpperCaseCharacter c = + // if IsUpper and IsLower return the same value, then we can't tell if it's upper or lower case, so ensure it is a letter + // otherwise it is bicameral, so must be upper case + let isUpper = Char.IsUpper c + + if isUpper = Char.IsLower c then + Char.IsLetter c + else + isUpper + + s.Length >= 1 && isUpperCaseCharacter s[0] + + let capitalize (s: string) = + if s.Length = 0 then + s + else + uppercase s[0..0] + s[1 .. s.Length - 1] + + let uncapitalize (s: string) = + if s.Length = 0 then + s + else + lowercase s[0..0] + s[1 .. s.Length - 1] + + let dropPrefix (s: string) (t: string) = s[t.Length .. s.Length - 1] + + let dropSuffix (s: string) (t: string) = s[0 .. s.Length - t.Length - 1] + + let inline toCharArray (str: string) = str.ToCharArray() + + let lowerCaseFirstChar (str: string) = + if String.IsNullOrEmpty str || Char.IsLower(str, 0) then + str + else + let strArr = toCharArray str + + match Array.tryHead strArr with + | None -> str + | Some c -> + strArr[0] <- Char.ToLower c + String strArr + + let extractTrailingIndex (str: string) = + let charr = str.ToCharArray() + Array.revInPlace charr + let digits = Array.takeWhile Char.IsDigit charr + Array.revInPlace digits + + String digits + |> function + | "" -> str, None + | index -> str.Substring(0, str.Length - index.Length), Some(int index) + + /// Splits a string into substrings based on the strings in the array separators + let split options (separator: string[]) (value: string) = value.Split(separator, options) + + let (|StartsWith|_|) pattern value = + if String.IsNullOrWhiteSpace value then None + elif value.StartsWithOrdinal pattern then Some() + else None + + let (|Contains|_|) (pattern: string) value = + if String.IsNullOrWhiteSpace value then None + elif value.Contains pattern then Some() + else None + + let getLines (str: string) = + use reader = new StringReader(str) + + [| + let mutable line = reader.ReadLine() + + while not (isNull line) do + yield line + line <- reader.ReadLine() + + if str.EndsWithOrdinal("\n") then + // last trailing space not returned + // http://stackoverflow.com/questions/19365404/stringreader-omits-trailing-linebreak + yield String.Empty + |] + +module Dictionary = + let inline newWithSize (size: int) = + Dictionary<_, _>(size, HashIdentity.Structural) + + let inline ofList (xs: ('Key * 'Value) list) = + let t = Dictionary<_, _>(List.length xs, HashIdentity.Structural) + + for k, v in xs do + t.Add(k, v) + + t + +[] +type DictionaryExtensions() = + + [] + static member inline BagAdd(dic: Dictionary<'key, 'value list>, key: 'key, value: 'value) = + match dic.TryGetValue key with + | true, values -> dic[key] <- value :: values + | _ -> dic[key] <- [ value ] + + [] + static member inline BagExistsValueForKey(dic: Dictionary<'key, 'value list>, key: 'key, f: 'value -> bool) = + match dic.TryGetValue key with + | true, values -> values |> List.exists f + | _ -> false + +module Lazy = + let force (x: Lazy<'T>) = x.Force() + +//---------------------------------------------------------------------------- +// Single threaded execution and mutual exclusion + +/// Represents a permission active at this point in execution +type ExecutionToken = + interface + end + +/// Represents a token that indicates execution on the compilation thread, i.e. +/// - we have full access to the (partially mutable) TAST and TcImports data structures +/// - compiler execution may result in type provider invocations when resolving types and members +/// - we can access various caches in the SourceCodeServices +/// +/// Like other execution tokens this should be passed via argument passing and not captured/stored beyond +/// the lifetime of stack-based calls. This is not checked, it is a discipline within the compiler code. +[] +type CompilationThreadToken() = + interface ExecutionToken + +/// A base type for various types of tokens that must be passed when a lock is taken. +/// Each different static lock should declare a new subtype of this type. +type LockToken = + inherit ExecutionToken + +/// Represents a token that indicates execution on any of several potential user threads calling the F# compiler services. +[] +type AnyCallerThreadToken() = + interface ExecutionToken + +[] +module internal LockAutoOpens = + /// Represents a place where we are stating that execution on the compilation thread is required. The + /// reason why will be documented in a comment in the code at the callsite. + let RequireCompilationThread (_ctok: CompilationThreadToken) = () + + /// Represents a place in the compiler codebase where we are passed a CompilationThreadToken unnecessarily. + /// This represents code that may potentially not need to be executed on the compilation thread. + let DoesNotRequireCompilerThreadTokenAndCouldPossiblyBeMadeConcurrent (_ctok: CompilationThreadToken) = () + + /// Represents a place in the compiler codebase where we assume we are executing on a compilation thread + let AssumeCompilationThreadWithoutEvidence () = + Unchecked.defaultof + + let AnyCallerThread = Unchecked.defaultof + + let AssumeLockWithoutEvidence<'LockTokenType when 'LockTokenType :> LockToken> () = Unchecked.defaultof<'LockTokenType> + +/// Encapsulates a lock associated with a particular token-type representing the acquisition of that lock. +type Lock<'LockTokenType when 'LockTokenType :> LockToken>() = + let lockObj = obj () + + member _.AcquireLock f = + lock lockObj (fun () -> f (AssumeLockWithoutEvidence<'LockTokenType>())) + +//--------------------------------------------------- +// Misc + +module Map = + let tryFindMulti k map = + match Map.tryFind k map with + | Some res -> res + | None -> [] + +[] +type ResultOrException<'TResult> = + | Result of result: 'TResult + | Exception of ``exception``: Exception + +module ResultOrException = + + let success a = Result a + + let raze (b: exn) = Exception b + + // map + let (|?>) res f = + match res with + | Result x -> Result(f x) + | Exception err -> Exception err + + let ForceRaise res = + match res with + | Result x -> x + | Exception err -> raise err + + let otherwise f x = + match x with + | Result x -> success x + | Exception _err -> f () + +[] +type ValueOrCancelled<'TResult> = + | Value of result: 'TResult + | Cancelled of ``exception``: OperationCanceledException + +/// Represents a cancellable computation with explicit representation of a cancelled result. +/// +/// A cancellable computation is passed may be cancelled via a CancellationToken, which is propagated implicitly. +/// If cancellation occurs, it is propagated as data rather than by raising an OperationCanceledException. +[] +type Cancellable<'T> = Cancellable of (CancellationToken -> ValueOrCancelled<'T>) + +module Cancellable = + + /// Run a cancellable computation using the given cancellation token + let inline run (ct: CancellationToken) (Cancellable oper) = + if ct.IsCancellationRequested then + ValueOrCancelled.Cancelled(OperationCanceledException ct) + else + oper ct + + let fold f acc seq = + Cancellable(fun ct -> + let mutable acc = ValueOrCancelled.Value acc + + for x in seq do + match acc with + | ValueOrCancelled.Value accv -> acc <- run ct (f accv x) + | ValueOrCancelled.Cancelled _ -> () + + acc) + + /// Run the computation in a mode where it may not be cancelled. The computation never results in a + /// ValueOrCancelled.Cancelled. + let runWithoutCancellation comp = + let res = run CancellationToken.None comp + + match res with + | ValueOrCancelled.Cancelled _ -> failwith "unexpected cancellation" + | ValueOrCancelled.Value r -> r + + let toAsync c = + async { + let! ct = Async.CancellationToken + let res = run ct c + + return! + Async.FromContinuations(fun (cont, _econt, ccont) -> + match res with + | ValueOrCancelled.Value v -> cont v + | ValueOrCancelled.Cancelled ce -> ccont ce) + } + + /// Bind the cancellation token associated with the computation + let token () = + Cancellable(fun ct -> ValueOrCancelled.Value ct) + + /// Represents a canceled computation + let canceled () = + Cancellable(fun ct -> ValueOrCancelled.Cancelled(OperationCanceledException ct)) + +type CancellableBuilder() = + + member inline _.Delay([] f) = + Cancellable(fun ct -> + let (Cancellable g) = f () + g ct) + + member inline _.Bind(comp, [] k) = + Cancellable(fun ct -> + + match Cancellable.run ct comp with + | ValueOrCancelled.Value v1 -> Cancellable.run ct (k v1) + | ValueOrCancelled.Cancelled err1 -> ValueOrCancelled.Cancelled err1) + + member inline _.BindReturn(comp, [] k) = + Cancellable(fun ct -> + + match Cancellable.run ct comp with + | ValueOrCancelled.Value v1 -> ValueOrCancelled.Value(k v1) + | ValueOrCancelled.Cancelled err1 -> ValueOrCancelled.Cancelled err1) + + member inline _.Combine(comp1, comp2) = + Cancellable(fun ct -> + + match Cancellable.run ct comp1 with + | ValueOrCancelled.Value () -> Cancellable.run ct comp2 + | ValueOrCancelled.Cancelled err1 -> ValueOrCancelled.Cancelled err1) + + member inline _.TryWith(comp, [] handler) = + Cancellable(fun ct -> + + let compRes = + try + match Cancellable.run ct comp with + | ValueOrCancelled.Value res -> ValueOrCancelled.Value(Choice1Of2 res) + | ValueOrCancelled.Cancelled exn -> ValueOrCancelled.Cancelled exn + with err -> + ValueOrCancelled.Value(Choice2Of2 err) + + match compRes with + | ValueOrCancelled.Value res -> + match res with + | Choice1Of2 r -> ValueOrCancelled.Value r + | Choice2Of2 err -> Cancellable.run ct (handler err) + | ValueOrCancelled.Cancelled err1 -> ValueOrCancelled.Cancelled err1) + + member inline _.Using(resource, [] comp) = + Cancellable(fun ct -> + let body = comp resource + + let compRes = + try + match Cancellable.run ct body with + | ValueOrCancelled.Value res -> ValueOrCancelled.Value(Choice1Of2 res) + | ValueOrCancelled.Cancelled exn -> ValueOrCancelled.Cancelled exn + with err -> + ValueOrCancelled.Value(Choice2Of2 err) + + match compRes with + | ValueOrCancelled.Value res -> + (resource :> IDisposable).Dispose() + + match res with + | Choice1Of2 r -> ValueOrCancelled.Value r + | Choice2Of2 err -> raise err + | ValueOrCancelled.Cancelled err1 -> ValueOrCancelled.Cancelled err1) + + member inline _.TryFinally(comp, [] compensation) = + Cancellable(fun ct -> + + let compRes = + try + match Cancellable.run ct comp with + | ValueOrCancelled.Value res -> ValueOrCancelled.Value(Choice1Of2 res) + | ValueOrCancelled.Cancelled exn -> ValueOrCancelled.Cancelled exn + with err -> + ValueOrCancelled.Value(Choice2Of2 err) + + match compRes with + | ValueOrCancelled.Value res -> + compensation () + + match res with + | Choice1Of2 r -> ValueOrCancelled.Value r + | Choice2Of2 err -> raise err + | ValueOrCancelled.Cancelled err1 -> ValueOrCancelled.Cancelled err1) + + member inline _.Return v = + Cancellable(fun _ -> ValueOrCancelled.Value v) + + member inline _.ReturnFrom(v: Cancellable<'T>) = v + + member inline _.Zero() = + Cancellable(fun _ -> ValueOrCancelled.Value()) + +[] +module CancellableAutoOpens = + let cancellable = CancellableBuilder() + +/// Generates unique stamps +type UniqueStampGenerator<'T when 'T: equality>() = + let gate = obj () + let encodeTab = ConcurrentDictionary<'T, int>(HashIdentity.Structural) + let mutable nItems = 0 + + let encode str = + match encodeTab.TryGetValue str with + | true, idx -> idx + | _ -> + lock gate (fun () -> + let idx = nItems + encodeTab[str] <- idx + nItems <- nItems + 1 + idx) + + member _.Encode str = encode str + + member _.Table = encodeTab.Keys + +/// memoize tables (all entries cached, never collected) +type MemoizationTable<'T, 'U>(compute: 'T -> 'U, keyComparer: IEqualityComparer<'T>, ?canMemoize) = + + let table = new ConcurrentDictionary<'T, 'U>(keyComparer) + + member t.Apply x = + if + (match canMemoize with + | None -> true + | Some f -> f x) + then + match table.TryGetValue x with + | true, res -> res + | _ -> + lock table (fun () -> + match table.TryGetValue x with + | true, res -> res + | _ -> + let res = compute x + table[x] <- res + res) + else + compute x + +exception UndefinedException + +type LazyWithContextFailure(exn: exn) = + + static let undefined = LazyWithContextFailure(UndefinedException) + + member _.Exception = exn + + static member Undefined = undefined + +/// Just like "Lazy" but EVERY forcer must provide an instance of "ctxt", e.g. to help track errors +/// on forcing back to at least one sensible user location +[] +[] +type LazyWithContext<'T, 'Ctxt> = + { + /// This field holds the result of a successful computation. It's initial value is Unchecked.defaultof + mutable value: 'T + + /// This field holds either the function to run or a LazyWithContextFailure object recording the exception raised + /// from running the function. It is null if the thunk has been evaluated successfully. + mutable funcOrException: obj + + /// A helper to ensure we rethrow the "original" exception + findOriginalException: exn -> exn + } + + static member Create(f: 'Ctxt -> 'T, findOriginalException) : LazyWithContext<'T, 'Ctxt> = + { + value = Unchecked.defaultof<'T> + funcOrException = box f + findOriginalException = findOriginalException + } + + static member NotLazy(x: 'T) : LazyWithContext<'T, 'Ctxt> = + { + value = x + funcOrException = null + findOriginalException = id + } + + member x.IsDelayed = + (match x.funcOrException with + | null -> false + | :? LazyWithContextFailure -> false + | _ -> true) + + member x.IsForced = + (match x.funcOrException with + | null -> true + | _ -> false) + + member x.Force(ctxt: 'Ctxt) = + match x.funcOrException with + | null -> x.value + | _ -> + // Enter the lock in case another thread is in the process of evaluating the result + Monitor.Enter x + + try + x.UnsynchronizedForce ctxt + finally + Monitor.Exit x + + member x.UnsynchronizedForce ctxt = + match x.funcOrException with + | null -> x.value + | :? LazyWithContextFailure as res -> + // Re-raise the original exception + raise (x.findOriginalException res.Exception) + | :? ('Ctxt -> 'T) as f -> + x.funcOrException <- box (LazyWithContextFailure.Undefined) + + try + let res = f ctxt + x.value <- res + x.funcOrException <- null + res + with exn -> + x.funcOrException <- box (LazyWithContextFailure(exn)) + reraise () + | _ -> failwith "unreachable" + +/// Intern tables to save space. +module Tables = + let memoize f = + let t = + ConcurrentDictionary<_, _>(Environment.ProcessorCount, 1000, HashIdentity.Structural) + + fun x -> + match t.TryGetValue x with + | true, res -> res + | _ -> + let res = f x + t[x] <- res + res + +/// Interface that defines methods for comparing objects using partial equality relation +type IPartialEqualityComparer<'T> = + inherit IEqualityComparer<'T> + /// Can the specified object be tested for equality? + abstract InEqualityRelation: 'T -> bool + +module IPartialEqualityComparer = + + let On f (c: IPartialEqualityComparer<_>) = + { new IPartialEqualityComparer<_> with + member _.InEqualityRelation x = c.InEqualityRelation(f x) + member _.Equals(x, y) = c.Equals(f x, f y) + member _.GetHashCode x = c.GetHashCode(f x) + } + + // Wrapper type for use by the 'partialDistinctBy' function + [] + type private WrapType<'T> = Wrap of 'T + + // Like Seq.distinctBy but only filters out duplicates for some of the elements + let partialDistinctBy (per: IPartialEqualityComparer<'T>) seq = + let wper = + { new IPartialEqualityComparer> with + member _.InEqualityRelation(Wrap x) = per.InEqualityRelation x + member _.Equals(Wrap x, Wrap y) = per.Equals(x, y) + member _.GetHashCode(Wrap x) = per.GetHashCode x + } + // Wrap a Wrap _ around all keys in case the key type is itself a type using null as a representation + let dict = Dictionary, obj>(wper) + + seq + |> List.filter (fun v -> + let key = Wrap v + + if (per.InEqualityRelation v) then + if dict.ContainsKey key then + false + else + (dict[key] <- null + true) + else + true) + +//------------------------------------------------------------------------- +// Library: Name maps +//------------------------------------------------------------------------ + +type NameMap<'T> = Map + +type NameMultiMap<'T> = NameMap<'T list> + +type MultiMap<'T, 'U when 'T: comparison> = Map<'T, 'U list> + +module NameMap = + + let empty = Map.empty + + let range m = + List.rev (Map.foldBack (fun _ x sofar -> x :: sofar) m []) + + let foldBack f (m: NameMap<'T>) z = Map.foldBack f m z + + let forall f m = + Map.foldBack (fun x y sofar -> sofar && f x y) m true + + let exists f m = + Map.foldBack (fun x y sofar -> sofar || f x y) m false + + let ofKeyedList f l = + List.foldBack (fun x acc -> Map.add (f x) x acc) l Map.empty + + let ofList l : NameMap<'T> = Map.ofList l + + let ofSeq l : NameMap<'T> = Map.ofSeq l + + let toList (l: NameMap<'T>) = Map.toList l + + let layer (m1: NameMap<'T>) m2 = Map.foldBack Map.add m1 m2 + + /// Not a very useful function - only called in one place - should be changed + let layerAdditive addf m1 m2 = + Map.foldBack (fun x y sofar -> Map.add x (addf (Map.tryFindMulti x sofar) y) sofar) m1 m2 + + /// Union entries by identical key, using the provided function to union sets of values + let union unionf (ms: NameMap<_> seq) = + seq { + for m in ms do + yield! m + } + |> Seq.groupBy (fun (KeyValue (k, _v)) -> k) + |> Seq.map (fun (k, es) -> (k, unionf (Seq.map (fun (KeyValue (_k, v)) -> v) es))) + |> Map.ofSeq + + /// For every entry in m2 find an entry in m1 and fold + let subfold2 errf f m1 m2 acc = + Map.foldBack + (fun n x2 acc -> + try + f n (Map.find n m1) x2 acc + with :? KeyNotFoundException -> + errf n x2) + m2 + acc + + let suball2 errf p m1 m2 = + subfold2 errf (fun _ x1 x2 acc -> p x1 x2 && acc) m1 m2 true + + let mapFold f s (l: NameMap<'T>) = + Map.foldBack (fun x y (l2, sx) -> let y2, sy = f sx x y in Map.add x y2 l2, sy) l (Map.empty, s) + + let foldBackRange f (l: NameMap<'T>) acc = + Map.foldBack (fun _ y acc -> f y acc) l acc + + let filterRange f (l: NameMap<'T>) = + Map.foldBack (fun x y acc -> if f y then Map.add x y acc else acc) l Map.empty + + let mapFilter f (l: NameMap<'T>) = + Map.foldBack + (fun x y acc -> + match f y with + | None -> acc + | Some y' -> Map.add x y' acc) + l + Map.empty + + let map f (l: NameMap<'T>) = Map.map (fun _ x -> f x) l + + let iter f (l: NameMap<'T>) = Map.iter (fun _k v -> f v) l + + let partition f (l: NameMap<'T>) = + Map.filter (fun _ x -> f x) l, Map.filter (fun _ x -> not (f x)) l + + let mem v (m: NameMap<'T>) = Map.containsKey v m + + let find v (m: NameMap<'T>) = Map.find v m + + let tryFind v (m: NameMap<'T>) = Map.tryFind v m + + let add v x (m: NameMap<'T>) = Map.add v x m + + let isEmpty (m: NameMap<'T>) = (Map.isEmpty m) + + let existsInRange p m = + Map.foldBack (fun _ y acc -> acc || p y) m false + + let tryFindInRange p m = + Map.foldBack + (fun _ y acc -> + match acc with + | None -> if p y then Some y else None + | _ -> acc) + m + None + +module NameMultiMap = + + let existsInRange f (m: NameMultiMap<'T>) = + NameMap.exists (fun _ l -> List.exists f l) m + + let find v (m: NameMultiMap<'T>) = + match m.TryGetValue v with + | true, r -> r + | _ -> [] + + let add v x (m: NameMultiMap<'T>) = NameMap.add v (x :: find v m) m + + let range (m: NameMultiMap<'T>) = + Map.foldBack (fun _ x sofar -> x @ sofar) m [] + + let rangeReversingEachBucket (m: NameMultiMap<'T>) = + Map.foldBack (fun _ x sofar -> List.rev x @ sofar) m [] + + let chooseRange f (m: NameMultiMap<'T>) = + Map.foldBack (fun _ x sofar -> List.choose f x @ sofar) m [] + + let map f (m: NameMultiMap<'T>) = NameMap.map (List.map f) m + + let empty: NameMultiMap<'T> = Map.empty + + let initBy f xs : NameMultiMap<'T> = + xs |> Seq.groupBy f |> Seq.map (fun (k, v) -> (k, List.ofSeq v)) |> Map.ofSeq + + let ofList (xs: (string * 'T) list) : NameMultiMap<'T> = + xs + |> Seq.groupBy fst + |> Seq.map (fun (k, v) -> (k, List.ofSeq (Seq.map snd v))) + |> Map.ofSeq + +module MultiMap = + + let existsInRange f (m: MultiMap<_, _>) = + Map.exists (fun _ l -> List.exists f l) m + + let find v (m: MultiMap<_, _>) = + match m.TryGetValue v with + | true, r -> r + | _ -> [] + + let add v x (m: MultiMap<_, _>) = Map.add v (x :: find v m) m + + let range (m: MultiMap<_, _>) = + Map.foldBack (fun _ x sofar -> x @ sofar) m [] + + let empty: MultiMap<_, _> = Map.empty + + let initBy f xs : MultiMap<_, _> = + xs |> Seq.groupBy f |> Seq.map (fun (k, v) -> (k, List.ofSeq v)) |> Map.ofSeq + +type LayeredMap<'Key, 'Value when 'Key: comparison> = Map<'Key, 'Value> + +[] +module MapAutoOpens = + type Map<'Key, 'Value when 'Key: comparison> with + + static member Empty: Map<'Key, 'Value> = Map.empty + +#if FSHARPCORE_USE_PACKAGE + member x.Values = [ for KeyValue (_, v) in x -> v ] +#endif + + member x.AddMany(kvs: _[]) = + (x, kvs) ||> Array.fold (fun x (KeyValue (k, v)) -> x.Add(k, v)) + + member x.AddOrModify(key, f: 'Value option -> 'Value) = x.Add(key, f (x.TryFind key)) + +/// Immutable map collection, with explicit flattening to a backing dictionary +[] +type LayeredMultiMap<'Key, 'Value when 'Key: equality and 'Key: comparison>(contents: LayeredMap<'Key, 'Value list>) = + + member x.Add(k, v) = + LayeredMultiMap(contents.Add(k, v :: x[k])) + + member _.Item + with get k = + match contents.TryGetValue k with + | true, l -> l + | _ -> [] + + member x.AddMany(kvs: _[]) = + (x, kvs) ||> Array.fold (fun x (KeyValue (k, v)) -> x.Add(k, v)) + + member _.TryFind k = contents.TryFind k + + member _.TryGetValue k = contents.TryGetValue k + + member _.Values = contents.Values |> List.concat + + static member Empty: LayeredMultiMap<'Key, 'Value> = LayeredMultiMap LayeredMap.Empty diff --git a/vsintegration/src/FSharp.Editor/FSharp.Editor.resx b/vsintegration/src/FSharp.Editor/FSharp.Editor.resx index 0766a3d3a71..9c8c315fe13 100644 --- a/vsintegration/src/FSharp.Editor/FSharp.Editor.resx +++ b/vsintegration/src/FSharp.Editor/FSharp.Editor.resx @@ -206,7 +206,8 @@ Enable stale data for IntelliSense features; Time until stale results are used (in milliseconds); Parallelization (requires restart); Enable parallel type checking with signature files; -Enable parallel reference resolution +Enable parallel reference resolution; +Enable fast find references & rename (experimental) Advanced diff --git a/vsintegration/src/FSharp.Editor/InlineRename/InlineRenameService.fs b/vsintegration/src/FSharp.Editor/InlineRename/InlineRenameService.fs index dfc56faa5f4..669c3ed8683 100644 --- a/vsintegration/src/FSharp.Editor/InlineRename/InlineRenameService.fs +++ b/vsintegration/src/FSharp.Editor/InlineRename/InlineRenameService.fs @@ -76,9 +76,8 @@ type internal InlineRenameInfo | true, text -> text | _ -> document.GetTextAsync(cancellationToken).Result - let symbolUses = - SymbolHelpers.getSymbolUsesInSolution(symbolUse.Symbol, declLoc, checkFileResults, document.Project.Solution) - |> Async.cache + let symbolUses ct = + SymbolHelpers.getSymbolUsesInSolution(symbolUse.Symbol, declLoc, checkFileResults, document.Project.Solution, ct) override _.CanRename = true override _.LocalizedErrorMessage = null @@ -104,7 +103,7 @@ type internal InlineRenameInfo override _.FindRenameLocationsAsync(_, _, cancellationToken) = async { - let! symbolUsesByDocumentId = symbolUses + let! symbolUsesByDocumentId = symbolUses cancellationToken let! locations = symbolUsesByDocumentId |> Seq.map (fun (KeyValue(documentId, symbolUses)) -> diff --git a/vsintegration/src/FSharp.Editor/LanguageService/SymbolHelpers.fs b/vsintegration/src/FSharp.Editor/LanguageService/SymbolHelpers.fs index 12dd5249787..f9202636eea 100644 --- a/vsintegration/src/FSharp.Editor/LanguageService/SymbolHelpers.fs +++ b/vsintegration/src/FSharp.Editor/LanguageService/SymbolHelpers.fs @@ -3,6 +3,7 @@ namespace Microsoft.VisualStudio.FSharp.Editor open System +open System.Collections.Concurrent open System.Collections.Immutable open System.Threading open System.Threading.Tasks @@ -10,7 +11,6 @@ open System.Threading.Tasks open Microsoft.CodeAnalysis open Microsoft.CodeAnalysis.Text -open FSharp.Compiler open FSharp.Compiler.CodeAnalysis open FSharp.Compiler.Symbols open FSharp.Compiler.Text @@ -36,12 +36,13 @@ module internal SymbolHelpers = return symbolUses } - let getSymbolUsesInProjects (symbol: FSharpSymbol, projects: Project list, onFound: Document -> TextSpan -> range -> Async) = + let getSymbolUsesInProjects (symbol: FSharpSymbol, projects: Project list, onFound: Document -> TextSpan -> range -> Async, ct: CancellationToken) = projects - |> Seq.map (fun project -> project.FindFSharpReferencesAsync(symbol, onFound, "getSymbolUsesInProjects")) - |> Async.Parallel + |> Seq.map (fun project -> + Task.Run(fun () -> project.FindFSharpReferencesAsync(symbol, onFound, "getSymbolUsesInProjects", ct))) + |> Task.WhenAll - let getSymbolUsesInSolution (symbol: FSharpSymbol, declLoc: SymbolDeclarationLocation, checkFileResults: FSharpCheckFileResults, solution: Solution) = + let getSymbolUsesInSolution (symbol: FSharpSymbol, declLoc: SymbolDeclarationLocation, checkFileResults: FSharpCheckFileResults, solution: Solution, ct: CancellationToken) = async { let toDict (symbolUseRanges: range seq) = let groups = @@ -59,7 +60,7 @@ module internal SymbolHelpers = let symbolUses = checkFileResults.GetUsesOfSymbolInFile(symbol, ct) return toDict (symbolUses |> Seq.map (fun symbolUse -> symbolUse.Range)) | SymbolDeclarationLocation.Projects (projects, isInternalToProject) -> - let symbolUseRanges = ImmutableArray.CreateBuilder() + let symbolUseRanges = ConcurrentBag() let projects = if isInternalToProject then projects @@ -73,11 +74,11 @@ module internal SymbolHelpers = fun _ _ symbolUseRange -> async { symbolUseRanges.Add symbolUseRange } - let! _ = getSymbolUsesInProjects (symbol, projects, onFound) + do! getSymbolUsesInProjects (symbol, projects, onFound, ct) |> Async.AwaitTask // Distinct these down because each TFM will produce a new 'project'. // Unless guarded by a #if define, symbols with the same range will be added N times - let symbolUseRanges = symbolUseRanges.ToArray() |> Array.distinct + let symbolUseRanges = symbolUseRanges |> Seq.distinct return toDict symbolUseRanges } @@ -115,7 +116,7 @@ module internal SymbolHelpers = Func<_,_>(fun (cancellationToken: CancellationToken) -> async { let! symbolUsesByDocumentId = - getSymbolUsesInSolution(symbolUse.Symbol, declLoc, checkFileResults, document.Project.Solution) + getSymbolUsesInSolution(symbolUse.Symbol, declLoc, checkFileResults, document.Project.Solution, cancellationToken) let mutable solution = document.Project.Solution diff --git a/vsintegration/src/FSharp.Editor/LanguageService/WorkspaceExtensions.fs b/vsintegration/src/FSharp.Editor/LanguageService/WorkspaceExtensions.fs index 8b067f72a47..f25746325b2 100644 --- a/vsintegration/src/FSharp.Editor/LanguageService/WorkspaceExtensions.fs +++ b/vsintegration/src/FSharp.Editor/LanguageService/WorkspaceExtensions.fs @@ -4,9 +4,11 @@ module internal Microsoft.VisualStudio.FSharp.Editor.WorkspaceExtensions open System open System.Runtime.CompilerServices open System.Threading +open System.Threading.Tasks open Microsoft.CodeAnalysis open FSharp.Compiler open FSharp.Compiler.CodeAnalysis +open FSharp.Compiler.Symbols [] module private CheckerExtensions = @@ -183,10 +185,12 @@ type Document with member this.FindFSharpReferencesAsync(symbol, onFound, userOpName) = async { let! checker, _, _, projectOptions = this.GetFSharpCompilationOptionsAsync(userOpName) - let! symbolUses = checker.FindBackgroundReferencesInFile(this.FilePath, projectOptions, symbol, canInvalidateProject = false) + let! symbolUses = checker.FindBackgroundReferencesInFile(this.FilePath, projectOptions, symbol, + canInvalidateProject = false, + fastCheck = this.Project.IsFastFindReferencesEnabled) let! ct = Async.CancellationToken let! sourceText = this.GetTextAsync ct |> Async.AwaitTask - for symbolUse in symbolUses do + for symbolUse in symbolUses do match RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, symbolUse) with | Some textSpan -> do! onFound textSpan symbolUse @@ -215,8 +219,31 @@ type Document with type Project with /// Find F# references in the given project. - member this.FindFSharpReferencesAsync(symbol, onFound, userOpName) = - async { - for doc in this.Documents do + member this.FindFSharpReferencesAsync(symbol: FSharpSymbol, onFound, userOpName, ct) : Task = backgroundTask { + + let declarationLocation = symbol.SignatureLocation |> Option.map Some |> Option.defaultValue symbol.DeclarationLocation + let declarationDocument = declarationLocation |> Option.bind this.Solution.TryGetDocumentFromFSharpRange + + let! canSkipDocuments = + match declarationDocument with + | Some document when this.IsFastFindReferencesEnabled && document.Project = this -> + backgroundTask { + let! _, _, _, options = document.GetFSharpCompilationOptionsAsync(userOpName) |> RoslynHelpers.StartAsyncAsTask ct + return options.SourceFiles |> Seq.takeWhile ((<>) document.FilePath) |> Set + } + | _ -> Task.FromResult Set.empty + + let documents = this.Documents |> Seq.filter (fun document -> not (canSkipDocuments.Contains document.FilePath)) + + if this.IsFastFindReferencesEnabled then + do! documents + |> Seq.map (fun doc -> + Task.Run(fun () -> + doc.FindFSharpReferencesAsync(symbol, (fun textSpan range -> onFound doc textSpan range), userOpName) + |> RoslynHelpers.StartAsyncUnitAsTask ct)) + |> Task.WhenAll + else + for doc in documents do do! doc.FindFSharpReferencesAsync(symbol, (fun textSpan range -> onFound doc textSpan range), userOpName) - } + |> RoslynHelpers.StartAsyncAsTask ct + } diff --git a/vsintegration/src/FSharp.Editor/Navigation/FindUsagesService.fs b/vsintegration/src/FSharp.Editor/Navigation/FindUsagesService.fs index 2819eb65d38..9e7557b9d7a 100644 --- a/vsintegration/src/FSharp.Editor/Navigation/FindUsagesService.fs +++ b/vsintegration/src/FSharp.Editor/Navigation/FindUsagesService.fs @@ -121,9 +121,8 @@ type internal FSharpFindUsagesService // The symbol is declared in .NET framework, an external assembly or in a C# project within the solution. // In order to find all its usages we have to check all F# projects. | _ -> Seq.toList document.Project.Solution.Projects - - let! _ = SymbolHelpers.getSymbolUsesInProjects (symbolUse.Symbol, projectsToCheck, onFound) |> liftAsync - () + let! ct = Async.CancellationToken |> liftAsync + do! SymbolHelpers.getSymbolUsesInProjects (symbolUse.Symbol, projectsToCheck, onFound, ct) |> Async.AwaitTask |> liftAsync } |> Async.Ignore interface IFSharpFindUsagesService with diff --git a/vsintegration/src/FSharp.Editor/Options/EditorOptions.fs b/vsintegration/src/FSharp.Editor/Options/EditorOptions.fs index 9e51ec9fb08..880a1c2cabc 100644 --- a/vsintegration/src/FSharp.Editor/Options/EditorOptions.fs +++ b/vsintegration/src/FSharp.Editor/Options/EditorOptions.fs @@ -59,18 +59,20 @@ type CodeFixesOptions = SuggestNamesForErrors = true } [] -type LanguageServicePerformanceOptions = +type LanguageServicePerformanceOptions = { EnableInMemoryCrossProjectReferences: bool AllowStaleCompletionResults: bool TimeUntilStaleCompletion: int EnableParallelCheckingWithSignatureFiles: bool - EnableParallelReferenceResolution: bool } + EnableParallelReferenceResolution: bool + EnableFastFindReferences: bool } static member Default = { EnableInMemoryCrossProjectReferences = true AllowStaleCompletionResults = true TimeUntilStaleCompletion = 2000 // In ms, so this is 2 seconds EnableParallelCheckingWithSignatureFiles = false - EnableParallelReferenceResolution = false } + EnableParallelReferenceResolution = false + EnableFastFindReferences = false } [] type AdvancedOptions = @@ -180,50 +182,36 @@ module EditorOptionsExtensions = type Project with - member this.AreFSharpInMemoryCrossProjectReferencesEnabled = + member private this.GetEditorOptions f fallback = let editorOptions = this.Solution.Workspace.Services.GetService() + match box editorOptions with - | null -> true - | _ -> editorOptions.LanguageServicePerformance.EnableInMemoryCrossProjectReferences + | null -> fallback + | _ -> f editorOptions + + member this.AreFSharpInMemoryCrossProjectReferencesEnabled = + this.GetEditorOptions (fun o -> o.LanguageServicePerformance.EnableInMemoryCrossProjectReferences) true member this.IsFSharpCodeFixesAlwaysPlaceOpensAtTopLevelEnabled = - let editorOptions = this.Solution.Workspace.Services.GetService() - match box editorOptions with - | null -> false - | _ -> editorOptions.CodeFixes.AlwaysPlaceOpensAtTopLevel + this.GetEditorOptions (fun o -> o.CodeFixes.AlwaysPlaceOpensAtTopLevel) false member this.IsFSharpCodeFixesUnusedDeclarationsEnabled = - let editorOptions = this.Solution.Workspace.Services.GetService() - match box editorOptions with - | null -> false - | _ -> editorOptions.CodeFixes.UnusedDeclarations + this.GetEditorOptions (fun o -> o.CodeFixes.UnusedDeclarations) false member this.IsFSharpStaleCompletionResultsEnabled = - let editorOptions = this.Solution.Workspace.Services.GetService() - match box editorOptions with - | null -> false - | _ -> editorOptions.LanguageServicePerformance.AllowStaleCompletionResults + this.GetEditorOptions (fun o -> o.LanguageServicePerformance.AllowStaleCompletionResults) false member this.FSharpTimeUntilStaleCompletion = - let editorOptions = this.Solution.Workspace.Services.GetService() - match box editorOptions with - | null -> 0 - | _ -> editorOptions.LanguageServicePerformance.TimeUntilStaleCompletion + this.GetEditorOptions (fun o -> o.LanguageServicePerformance.TimeUntilStaleCompletion) 0 member this.IsFSharpCodeFixesSimplifyNameEnabled = - let editorOptions = this.Solution.Workspace.Services.GetService() - match box editorOptions with - | null -> false - | _ -> editorOptions.CodeFixes.SimplifyName + this.GetEditorOptions (fun o -> o.CodeFixes.SimplifyName) false member this.IsFSharpCodeFixesUnusedOpensEnabled = - let editorOptions = this.Solution.Workspace.Services.GetService() - match box editorOptions with - | null -> false - | _ -> editorOptions.CodeFixes.UnusedOpens + this.GetEditorOptions (fun o -> o.CodeFixes.UnusedOpens) false member this.IsFSharpBlockStructureEnabled = - let editorOptions = this.Solution.Workspace.Services.GetService() - match box editorOptions with - | null -> false - | _ -> editorOptions.Advanced.IsBlockStructureEnabled \ No newline at end of file + this.GetEditorOptions (fun o -> o.Advanced.IsBlockStructureEnabled) false + + member this.IsFastFindReferencesEnabled = + this.GetEditorOptions (fun o -> o.LanguageServicePerformance.EnableFastFindReferences) false diff --git a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.cs.xlf b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.cs.xlf index e36b7e66775..fd11eefbffb 100644 --- a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.cs.xlf +++ b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.cs.xlf @@ -154,7 +154,8 @@ Enable stale data for IntelliSense features; Time until stale results are used (in milliseconds); Parallelization (requires restart); Enable parallel type checking with signature files; -Enable parallel reference resolution +Enable parallel reference resolution; +Enable fast find references & rename (experimental) F# Project and Caching Performance Options; Enable in-memory cross project references; IntelliSense Performance Options; @@ -162,7 +163,8 @@ Enable stale data for IntelliSense features; Time until stale results are used (in milliseconds); Parallelization (requires restart); Enable parallel type checking with signature files; -Enable parallel reference resolution +Enable parallel reference resolution; +Enable fast find references & rename (experimental) diff --git a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.de.xlf b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.de.xlf index 13fa2b0dda6..29aecb4e068 100644 --- a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.de.xlf +++ b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.de.xlf @@ -154,7 +154,8 @@ Enable stale data for IntelliSense features; Time until stale results are used (in milliseconds); Parallelization (requires restart); Enable parallel type checking with signature files; -Enable parallel reference resolution +Enable parallel reference resolution; +Enable fast find references & rename (experimental) F# Project and Caching Performance Options; Enable in-memory cross project references; IntelliSense Performance Options; @@ -162,7 +163,8 @@ Enable stale data for IntelliSense features; Time until stale results are used (in milliseconds); Parallelization (requires restart); Enable parallel type checking with signature files; -Enable parallel reference resolution +Enable parallel reference resolution; +Enable fast find references & rename (experimental) diff --git a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.es.xlf b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.es.xlf index 576c0ae9d3b..1d43e6fe28b 100644 --- a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.es.xlf +++ b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.es.xlf @@ -154,7 +154,8 @@ Enable stale data for IntelliSense features; Time until stale results are used (in milliseconds); Parallelization (requires restart); Enable parallel type checking with signature files; -Enable parallel reference resolution +Enable parallel reference resolution; +Enable fast find references & rename (experimental) F# Project and Caching Performance Options; Enable in-memory cross project references; IntelliSense Performance Options; @@ -162,7 +163,8 @@ Enable stale data for IntelliSense features; Time until stale results are used (in milliseconds); Parallelization (requires restart); Enable parallel type checking with signature files; -Enable parallel reference resolution +Enable parallel reference resolution; +Enable fast find references & rename (experimental) diff --git a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.fr.xlf b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.fr.xlf index afb8048fc9e..d127852260f 100644 --- a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.fr.xlf +++ b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.fr.xlf @@ -154,7 +154,8 @@ Enable stale data for IntelliSense features; Time until stale results are used (in milliseconds); Parallelization (requires restart); Enable parallel type checking with signature files; -Enable parallel reference resolution +Enable parallel reference resolution; +Enable fast find references & rename (experimental) F# Project and Caching Performance Options; Enable in-memory cross project references; IntelliSense Performance Options; @@ -162,7 +163,8 @@ Enable stale data for IntelliSense features; Time until stale results are used (in milliseconds); Parallelization (requires restart); Enable parallel type checking with signature files; -Enable parallel reference resolution +Enable parallel reference resolution; +Enable fast find references & rename (experimental) diff --git a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.it.xlf b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.it.xlf index 8299bd8e19b..e9058f4dc73 100644 --- a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.it.xlf +++ b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.it.xlf @@ -154,7 +154,8 @@ Enable stale data for IntelliSense features; Time until stale results are used (in milliseconds); Parallelization (requires restart); Enable parallel type checking with signature files; -Enable parallel reference resolution +Enable parallel reference resolution; +Enable fast find references & rename (experimental) F# Project and Caching Performance Options; Enable in-memory cross project references; IntelliSense Performance Options; @@ -162,7 +163,8 @@ Enable stale data for IntelliSense features; Time until stale results are used (in milliseconds); Parallelization (requires restart); Enable parallel type checking with signature files; -Enable parallel reference resolution +Enable parallel reference resolution; +Enable fast find references & rename (experimental) diff --git a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.ja.xlf b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.ja.xlf index 6cc0ebf8637..d21eadf50b3 100644 --- a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.ja.xlf +++ b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.ja.xlf @@ -154,7 +154,8 @@ Enable stale data for IntelliSense features; Time until stale results are used (in milliseconds); Parallelization (requires restart); Enable parallel type checking with signature files; -Enable parallel reference resolution +Enable parallel reference resolution; +Enable fast find references & rename (experimental) F# Project and Caching Performance Options; Enable in-memory cross project references; IntelliSense Performance Options; @@ -162,7 +163,8 @@ Enable stale data for IntelliSense features; Time until stale results are used (in milliseconds); Parallelization (requires restart); Enable parallel type checking with signature files; -Enable parallel reference resolution +Enable parallel reference resolution; +Enable fast find references & rename (experimental) diff --git a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.ko.xlf b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.ko.xlf index 0443036d007..e0a1add01b6 100644 --- a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.ko.xlf +++ b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.ko.xlf @@ -154,7 +154,8 @@ Enable stale data for IntelliSense features; Time until stale results are used (in milliseconds); Parallelization (requires restart); Enable parallel type checking with signature files; -Enable parallel reference resolution +Enable parallel reference resolution; +Enable fast find references & rename (experimental) F# Project and Caching Performance Options; Enable in-memory cross project references; IntelliSense Performance Options; @@ -162,7 +163,8 @@ Enable stale data for IntelliSense features; Time until stale results are used (in milliseconds); Parallelization (requires restart); Enable parallel type checking with signature files; -Enable parallel reference resolution +Enable parallel reference resolution; +Enable fast find references & rename (experimental) diff --git a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.pl.xlf b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.pl.xlf index c3a0a70229b..b85e0b53f3e 100644 --- a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.pl.xlf +++ b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.pl.xlf @@ -154,7 +154,8 @@ Enable stale data for IntelliSense features; Time until stale results are used (in milliseconds); Parallelization (requires restart); Enable parallel type checking with signature files; -Enable parallel reference resolution +Enable parallel reference resolution; +Enable fast find references & rename (experimental) F# Project and Caching Performance Options; Enable in-memory cross project references; IntelliSense Performance Options; @@ -162,7 +163,8 @@ Enable stale data for IntelliSense features; Time until stale results are used (in milliseconds); Parallelization (requires restart); Enable parallel type checking with signature files; -Enable parallel reference resolution +Enable parallel reference resolution; +Enable fast find references & rename (experimental) diff --git a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.pt-BR.xlf b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.pt-BR.xlf index 59dc2592f43..012625ddc09 100644 --- a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.pt-BR.xlf +++ b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.pt-BR.xlf @@ -154,7 +154,8 @@ Enable stale data for IntelliSense features; Time until stale results are used (in milliseconds); Parallelization (requires restart); Enable parallel type checking with signature files; -Enable parallel reference resolution +Enable parallel reference resolution; +Enable fast find references & rename (experimental) F# Project and Caching Performance Options; Enable in-memory cross project references; IntelliSense Performance Options; @@ -162,7 +163,8 @@ Enable stale data for IntelliSense features; Time until stale results are used (in milliseconds); Parallelization (requires restart); Enable parallel type checking with signature files; -Enable parallel reference resolution +Enable parallel reference resolution; +Enable fast find references & rename (experimental) diff --git a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.ru.xlf b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.ru.xlf index 91c779470a0..9724c82c1ec 100644 --- a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.ru.xlf +++ b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.ru.xlf @@ -154,7 +154,8 @@ Enable stale data for IntelliSense features; Time until stale results are used (in milliseconds); Parallelization (requires restart); Enable parallel type checking with signature files; -Enable parallel reference resolution +Enable parallel reference resolution; +Enable fast find references & rename (experimental) F# Project and Caching Performance Options; Enable in-memory cross project references; IntelliSense Performance Options; @@ -162,7 +163,8 @@ Enable stale data for IntelliSense features; Time until stale results are used (in milliseconds); Parallelization (requires restart); Enable parallel type checking with signature files; -Enable parallel reference resolution +Enable parallel reference resolution; +Enable fast find references & rename (experimental) diff --git a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.tr.xlf b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.tr.xlf index 9b667b51a33..cd87fa4f329 100644 --- a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.tr.xlf +++ b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.tr.xlf @@ -154,7 +154,8 @@ Enable stale data for IntelliSense features; Time until stale results are used (in milliseconds); Parallelization (requires restart); Enable parallel type checking with signature files; -Enable parallel reference resolution +Enable parallel reference resolution; +Enable fast find references & rename (experimental) F# Project and Caching Performance Options; Enable in-memory cross project references; IntelliSense Performance Options; @@ -162,7 +163,8 @@ Enable stale data for IntelliSense features; Time until stale results are used (in milliseconds); Parallelization (requires restart); Enable parallel type checking with signature files; -Enable parallel reference resolution +Enable parallel reference resolution; +Enable fast find references & rename (experimental) diff --git a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.zh-Hans.xlf b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.zh-Hans.xlf index 8127a56f38c..f74663ee264 100644 --- a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.zh-Hans.xlf +++ b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.zh-Hans.xlf @@ -154,7 +154,8 @@ Enable stale data for IntelliSense features; Time until stale results are used (in milliseconds); Parallelization (requires restart); Enable parallel type checking with signature files; -Enable parallel reference resolution +Enable parallel reference resolution; +Enable fast find references & rename (experimental) F# Project and Caching Performance Options; Enable in-memory cross project references; IntelliSense Performance Options; @@ -162,7 +163,8 @@ Enable stale data for IntelliSense features; Time until stale results are used (in milliseconds); Parallelization (requires restart); Enable parallel type checking with signature files; -Enable parallel reference resolution +Enable parallel reference resolution; +Enable fast find references & rename (experimental) diff --git a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.zh-Hant.xlf b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.zh-Hant.xlf index e89bf1249de..783fa6c7329 100644 --- a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.zh-Hant.xlf +++ b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.zh-Hant.xlf @@ -154,7 +154,8 @@ Enable stale data for IntelliSense features; Time until stale results are used (in milliseconds); Parallelization (requires restart); Enable parallel type checking with signature files; -Enable parallel reference resolution +Enable parallel reference resolution; +Enable fast find references & rename (experimental) F# Project and Caching Performance Options; Enable in-memory cross project references; IntelliSense Performance Options; @@ -162,7 +163,8 @@ Enable stale data for IntelliSense features; Time until stale results are used (in milliseconds); Parallelization (requires restart); Enable parallel type checking with signature files; -Enable parallel reference resolution +Enable parallel reference resolution; +Enable fast find references & rename (experimental) diff --git a/vsintegration/src/FSharp.UIResources/LanguageServicePerformanceOptionControl.xaml b/vsintegration/src/FSharp.UIResources/LanguageServicePerformanceOptionControl.xaml index 26a38d423d5..b61f20c47f2 100644 --- a/vsintegration/src/FSharp.UIResources/LanguageServicePerformanceOptionControl.xaml +++ b/vsintegration/src/FSharp.UIResources/LanguageServicePerformanceOptionControl.xaml @@ -64,6 +64,13 @@ Content="{x:Static local:Strings.Enable_Parallel_Reference_Resolution}"/> + + + + + diff --git a/vsintegration/src/FSharp.UIResources/Strings.Designer.cs b/vsintegration/src/FSharp.UIResources/Strings.Designer.cs index dad1be8b346..0af8d70d3b4 100644 --- a/vsintegration/src/FSharp.UIResources/Strings.Designer.cs +++ b/vsintegration/src/FSharp.UIResources/Strings.Designer.cs @@ -123,6 +123,15 @@ public static string Dot_underline { } } + /// + /// Looks up a localized string similar to Enable fast find references & rename (experimental). + /// + public static string Enable_Fast_Find_References { + get { + return ResourceManager.GetString("Enable_Fast_Find_References", resourceCulture); + } + } + /// /// Looks up a localized string similar to _Enable in-memory cross project references. /// @@ -195,6 +204,15 @@ public static string Enter_Key_Rule { } } + /// + /// Looks up a localized string similar to Find References Performance Options. + /// + public static string Find_References_Performance { + get { + return ResourceManager.GetString("Find_References_Performance", resourceCulture); + } + } + /// /// Looks up a localized string similar to Re-format indentation on paste (Experimental). /// diff --git a/vsintegration/src/FSharp.UIResources/Strings.resx b/vsintegration/src/FSharp.UIResources/Strings.resx index d48c9526702..95e7f98231a 100644 --- a/vsintegration/src/FSharp.UIResources/Strings.resx +++ b/vsintegration/src/FSharp.UIResources/Strings.resx @@ -234,4 +234,10 @@ Display inline parameter name hints (experimental) + + Enable fast find references & rename (experimental) + + + Find References Performance Options + \ No newline at end of file diff --git a/vsintegration/src/FSharp.UIResources/xlf/Strings.cs.xlf b/vsintegration/src/FSharp.UIResources/xlf/Strings.cs.xlf index 14a5be50a2b..6be46211969 100644 --- a/vsintegration/src/FSharp.UIResources/xlf/Strings.cs.xlf +++ b/vsintegration/src/FSharp.UIResources/xlf/Strings.cs.xlf @@ -7,6 +7,16 @@ Vždy umístit otevřené příkazy na nejvyšší úroveň + + Enable fast find references & rename (experimental) + Enable fast find references & rename (experimental) + + + + Find References Performance Options + Find References Performance Options + + Inline Hints Inline Hints diff --git a/vsintegration/src/FSharp.UIResources/xlf/Strings.de.xlf b/vsintegration/src/FSharp.UIResources/xlf/Strings.de.xlf index c54b2953101..3011cc25b9c 100644 --- a/vsintegration/src/FSharp.UIResources/xlf/Strings.de.xlf +++ b/vsintegration/src/FSharp.UIResources/xlf/Strings.de.xlf @@ -7,6 +7,16 @@ open-Anweisungen immer an oberster Ebene platzieren + + Enable fast find references & rename (experimental) + Enable fast find references & rename (experimental) + + + + Find References Performance Options + Find References Performance Options + + Inline Hints Inline Hints diff --git a/vsintegration/src/FSharp.UIResources/xlf/Strings.es.xlf b/vsintegration/src/FSharp.UIResources/xlf/Strings.es.xlf index a36a5e67414..35568eda7a0 100644 --- a/vsintegration/src/FSharp.UIResources/xlf/Strings.es.xlf +++ b/vsintegration/src/FSharp.UIResources/xlf/Strings.es.xlf @@ -7,6 +7,16 @@ Colocar siempre las instrucciones open en el nivel superior + + Enable fast find references & rename (experimental) + Enable fast find references & rename (experimental) + + + + Find References Performance Options + Find References Performance Options + + Inline Hints Inline Hints diff --git a/vsintegration/src/FSharp.UIResources/xlf/Strings.fr.xlf b/vsintegration/src/FSharp.UIResources/xlf/Strings.fr.xlf index 6f4222e5d34..68f57243011 100644 --- a/vsintegration/src/FSharp.UIResources/xlf/Strings.fr.xlf +++ b/vsintegration/src/FSharp.UIResources/xlf/Strings.fr.xlf @@ -7,6 +7,16 @@ Placer toujours les instructions open au niveau supérieur + + Enable fast find references & rename (experimental) + Enable fast find references & rename (experimental) + + + + Find References Performance Options + Find References Performance Options + + Inline Hints Inline Hints diff --git a/vsintegration/src/FSharp.UIResources/xlf/Strings.it.xlf b/vsintegration/src/FSharp.UIResources/xlf/Strings.it.xlf index 6506e23873e..523841ce7e2 100644 --- a/vsintegration/src/FSharp.UIResources/xlf/Strings.it.xlf +++ b/vsintegration/src/FSharp.UIResources/xlf/Strings.it.xlf @@ -7,6 +7,16 @@ Inserisci sempre le istruzioni OPEN al primo livello + + Enable fast find references & rename (experimental) + Enable fast find references & rename (experimental) + + + + Find References Performance Options + Find References Performance Options + + Inline Hints Inline Hints diff --git a/vsintegration/src/FSharp.UIResources/xlf/Strings.ja.xlf b/vsintegration/src/FSharp.UIResources/xlf/Strings.ja.xlf index 2bb8b5643d3..4f02151e44e 100644 --- a/vsintegration/src/FSharp.UIResources/xlf/Strings.ja.xlf +++ b/vsintegration/src/FSharp.UIResources/xlf/Strings.ja.xlf @@ -7,6 +7,16 @@ Open ステートメントを常に最上位に配置する + + Enable fast find references & rename (experimental) + Enable fast find references & rename (experimental) + + + + Find References Performance Options + Find References Performance Options + + Inline Hints Inline Hints diff --git a/vsintegration/src/FSharp.UIResources/xlf/Strings.ko.xlf b/vsintegration/src/FSharp.UIResources/xlf/Strings.ko.xlf index feae9fa0368..0ea54bedbba 100644 --- a/vsintegration/src/FSharp.UIResources/xlf/Strings.ko.xlf +++ b/vsintegration/src/FSharp.UIResources/xlf/Strings.ko.xlf @@ -7,6 +7,16 @@ 항상 최상위에 open 문 배치 + + Enable fast find references & rename (experimental) + Enable fast find references & rename (experimental) + + + + Find References Performance Options + Find References Performance Options + + Inline Hints Inline Hints diff --git a/vsintegration/src/FSharp.UIResources/xlf/Strings.pl.xlf b/vsintegration/src/FSharp.UIResources/xlf/Strings.pl.xlf index 9e1bb90b30e..c11bdce4a7b 100644 --- a/vsintegration/src/FSharp.UIResources/xlf/Strings.pl.xlf +++ b/vsintegration/src/FSharp.UIResources/xlf/Strings.pl.xlf @@ -7,6 +7,16 @@ Zawsze umieszczaj otwarte instrukcje na najwyższym poziomie + + Enable fast find references & rename (experimental) + Enable fast find references & rename (experimental) + + + + Find References Performance Options + Find References Performance Options + + Inline Hints Inline Hints diff --git a/vsintegration/src/FSharp.UIResources/xlf/Strings.pt-BR.xlf b/vsintegration/src/FSharp.UIResources/xlf/Strings.pt-BR.xlf index 32ed3a2e34b..50b7e6f8000 100644 --- a/vsintegration/src/FSharp.UIResources/xlf/Strings.pt-BR.xlf +++ b/vsintegration/src/FSharp.UIResources/xlf/Strings.pt-BR.xlf @@ -7,6 +7,16 @@ Sempre coloque as instruções abertas no nível superior + + Enable fast find references & rename (experimental) + Enable fast find references & rename (experimental) + + + + Find References Performance Options + Find References Performance Options + + Inline Hints Inline Hints diff --git a/vsintegration/src/FSharp.UIResources/xlf/Strings.ru.xlf b/vsintegration/src/FSharp.UIResources/xlf/Strings.ru.xlf index 459db8c25f2..4dcb753f0c9 100644 --- a/vsintegration/src/FSharp.UIResources/xlf/Strings.ru.xlf +++ b/vsintegration/src/FSharp.UIResources/xlf/Strings.ru.xlf @@ -7,6 +7,16 @@ Всегда располагайте открытые операторы на верхнем уровне + + Enable fast find references & rename (experimental) + Enable fast find references & rename (experimental) + + + + Find References Performance Options + Find References Performance Options + + Inline Hints Inline Hints diff --git a/vsintegration/src/FSharp.UIResources/xlf/Strings.tr.xlf b/vsintegration/src/FSharp.UIResources/xlf/Strings.tr.xlf index 47a2bcd6e03..4f4286abe68 100644 --- a/vsintegration/src/FSharp.UIResources/xlf/Strings.tr.xlf +++ b/vsintegration/src/FSharp.UIResources/xlf/Strings.tr.xlf @@ -7,6 +7,16 @@ Açık deyimleri her zaman en üst düzeye yerleştir + + Enable fast find references & rename (experimental) + Enable fast find references & rename (experimental) + + + + Find References Performance Options + Find References Performance Options + + Inline Hints Inline Hints diff --git a/vsintegration/src/FSharp.UIResources/xlf/Strings.zh-Hans.xlf b/vsintegration/src/FSharp.UIResources/xlf/Strings.zh-Hans.xlf index 5c03fc08051..379907d0d29 100644 --- a/vsintegration/src/FSharp.UIResources/xlf/Strings.zh-Hans.xlf +++ b/vsintegration/src/FSharp.UIResources/xlf/Strings.zh-Hans.xlf @@ -7,6 +7,16 @@ 始终在顶层放置 open 语句 + + Enable fast find references & rename (experimental) + Enable fast find references & rename (experimental) + + + + Find References Performance Options + Find References Performance Options + + Inline Hints Inline Hints diff --git a/vsintegration/src/FSharp.UIResources/xlf/Strings.zh-Hant.xlf b/vsintegration/src/FSharp.UIResources/xlf/Strings.zh-Hant.xlf index 008f0ac1995..155b2946c2b 100644 --- a/vsintegration/src/FSharp.UIResources/xlf/Strings.zh-Hant.xlf +++ b/vsintegration/src/FSharp.UIResources/xlf/Strings.zh-Hant.xlf @@ -7,6 +7,16 @@ 一律將 open 陳述式放在最上層 + + Enable fast find references & rename (experimental) + Enable fast find references & rename (experimental) + + + + Find References Performance Options + Find References Performance Options + + Inline Hints Inline Hints