From ca91c775a561a88784250a57f813e205957d9849 Mon Sep 17 00:00:00 2001 From: Phillip Carter Date: Wed, 2 Jan 2019 13:13:26 -0800 Subject: [PATCH 1/5] Use hashes for language service cache keys --- src/fsharp/service/service.fs | 42 +++++++++---------- src/utils/prim-lexing.fs | 4 ++ src/utils/prim-lexing.fsi | 4 +- .../src/FSharp.Editor/Common/Extensions.fs | 19 +++++++++ 4 files changed, 47 insertions(+), 22 deletions(-) diff --git a/src/fsharp/service/service.fs b/src/fsharp/service/service.fs index f46ad3629d1..8664dc33018 100644 --- a/src/fsharp/service/service.fs +++ b/src/fsharp/service/service.fs @@ -2188,17 +2188,17 @@ module Helpers = && FSharpProjectOptions.UseSameProject(o1,o2) /// Determine whether two (fileName,sourceText,options) keys should be identical w.r.t. parsing - let AreSameForParsing((fileName1: string, source1: ISourceText, options1), (fileName2, source2, options2)) = - fileName1 = fileName2 && options1 = options2 && source1.ContentEquals(source2) + let AreSameForParsing((fileName1: string, source1Hash: int, options1), (fileName2, source2Hash, options2)) = + fileName1 = fileName2 && options1 = options2 && source1Hash = source2Hash let AreSimilarForParsing((fileName1, _, _), (fileName2, _, _)) = fileName1 = fileName2 /// Determine whether two (fileName,sourceText,options) keys should be identical w.r.t. checking - let AreSameForChecking3((fileName1: string, source1: ISourceText, options1: FSharpProjectOptions), (fileName2, source2, options2)) = + let AreSameForChecking3((fileName1: string, source1Hash: int, options1: FSharpProjectOptions), (fileName2, source2Hash, options2)) = (fileName1 = fileName2) && FSharpProjectOptions.AreSameForChecking(options1,options2) - && source1.ContentEquals(source2) + && source1Hash = source2Hash /// Determine whether two (fileName,sourceText,options) keys should be identical w.r.t. resource usage let AreSubsumable3((fileName1:string,_,o1:FSharpProjectOptions),(fileName2:string,_,o2:FSharpProjectOptions)) = @@ -2314,7 +2314,7 @@ module CompileHelpers = System.Console.SetError error | None -> () - +type SourceTextHash = int type FileName = string type FilePath = string type ProjectPath = string @@ -2460,7 +2460,7 @@ type BackgroundCompiler(legacyReferenceResolver, projectCacheSize, keepAssemblyC // Also keyed on source. This can only be out of date if the antecedent is out of date let checkFileInProjectCache = - MruCache + MruCache (keepStrongly=checkFileInProjectCacheSize, areSame=AreSameForChecking3, areSimilar=AreSubsumable3) @@ -2511,13 +2511,13 @@ type BackgroundCompiler(legacyReferenceResolver, projectCacheSize, keepAssemblyC member bc.ParseFile(filename: string, sourceText: ISourceText, options: FSharpParsingOptions, userOpName: string) = async { - match parseCacheLock.AcquireLock(fun ltok -> parseFileCache.TryGet(ltok, (filename, sourceText, options))) with + match parseCacheLock.AcquireLock(fun ltok -> parseFileCache.TryGet(ltok, (filename, sourceText.GetHashCode(), options))) with | Some res -> return res | None -> foregroundParseCount <- foregroundParseCount + 1 let parseErrors, parseTreeOpt, anyErrors = Parser.parseFile(sourceText, filename, options, userOpName) let res = FSharpParseFileResults(parseErrors, parseTreeOpt, anyErrors, options.SourceFiles) - parseCacheLock.AcquireLock(fun ltok -> parseFileCache.Set(ltok, (filename, sourceText, options), res)) + parseCacheLock.AcquireLock(fun ltok -> parseFileCache.Set(ltok, (filename, sourceText.GetHashCode(), options), res)) return res } @@ -2536,9 +2536,9 @@ type BackgroundCompiler(legacyReferenceResolver, projectCacheSize, keepAssemblyC } ) - member bc.GetCachedCheckFileResult(builder: IncrementalBuilder,filename,sourceText,options) = + member bc.GetCachedCheckFileResult(builder: IncrementalBuilder,filename,sourceText: ISourceText,options) = // Check the cache. We can only use cached results when there is no work to do to bring the background builder up-to-date - let cachedResults = parseCacheLock.AcquireLock (fun ltok -> checkFileInProjectCache.TryGet(ltok, (filename,sourceText,options))) + let cachedResults = parseCacheLock.AcquireLock (fun ltok -> checkFileInProjectCache.TryGet(ltok, (filename,sourceText.GetHashCode(),options))) match cachedResults with // | Some (parseResults, checkResults, _, _) when builder.AreCheckResultsBeforeFileInProjectReady(filename) -> @@ -2598,7 +2598,7 @@ type BackgroundCompiler(legacyReferenceResolver, projectCacheSize, keepAssemblyC tcPrior.TcState, loadClosure, tcPrior.TcErrors, reactorOps, (fun () -> builder.IsAlive), textSnapshotInfo, userOpName) let parsingOptions = FSharpParsingOptions.FromTcConfig(tcPrior.TcConfig, Array.ofList builder.SourceFiles, options.UseScriptResolutionRules) let checkAnswer = MakeCheckFileAnswer(fileName, tcFileResult, options, builder, Array.ofList tcPrior.TcDependencyFiles, creationErrors, parseResults.Errors, tcErrors) - bc.RecordTypeCheckFileInProjectResults(fileName, options, parsingOptions, parseResults, fileVersion, tcPrior.TimeStamp, Some checkAnswer, sourceText) + bc.RecordTypeCheckFileInProjectResults(fileName, options, parsingOptions, parseResults, fileVersion, tcPrior.TimeStamp, Some checkAnswer, sourceText.GetHashCode()) return checkAnswer finally let dummy = ref () @@ -2614,7 +2614,7 @@ type BackgroundCompiler(legacyReferenceResolver, projectCacheSize, keepAssemblyC } /// Type-check the result obtained by parsing, but only if the antecedent type checking context is available. - member bc.CheckFileInProjectAllowingStaleCachedResults(parseResults: FSharpParseFileResults, filename, fileVersion, sourceText, options, textSnapshotInfo: obj option, userOpName) = + member bc.CheckFileInProjectAllowingStaleCachedResults(parseResults: FSharpParseFileResults, filename, fileVersion, sourceText: ISourceText, options, textSnapshotInfo: obj option, userOpName) = let execWithReactorAsync action = reactor.EnqueueAndAwaitOpAsync(userOpName, "CheckFileInProjectAllowingStaleCachedResults ", filename, action) async { try @@ -2629,7 +2629,7 @@ type BackgroundCompiler(legacyReferenceResolver, projectCacheSize, keepAssemblyC match incrementalBuildersCache.TryGetAny (ctok, options) with | Some (Some builder, creationErrors, _) -> - match bc.GetCachedCheckFileResult(builder, filename, sourceText, options) with + match bc.GetCachedCheckFileResult(builder, filename, sourceText.GetHashCode(), options) with | Some (_, checkResults) -> return Some (builder, creationErrors, Some (FSharpCheckFileAnswer.Succeeded checkResults)) | _ -> return Some (builder, creationErrors, None) | _ -> return None // the builder wasn't ready @@ -2657,7 +2657,7 @@ type BackgroundCompiler(legacyReferenceResolver, projectCacheSize, keepAssemblyC } /// Type-check the result obtained by parsing. Force the evaluation of the antecedent type checking context if needed. - member bc.CheckFileInProject(parseResults: FSharpParseFileResults, filename, fileVersion, sourceText, options, textSnapshotInfo, userOpName) = + member bc.CheckFileInProject(parseResults: FSharpParseFileResults, filename, fileVersion, sourceText: ISourceText, options, textSnapshotInfo, userOpName) = let execWithReactorAsync action = reactor.EnqueueAndAwaitOpAsync(userOpName, "CheckFileInProject", filename, action) async { try @@ -2669,7 +2669,7 @@ type BackgroundCompiler(legacyReferenceResolver, projectCacheSize, keepAssemblyC | None -> return FSharpCheckFileAnswer.Succeeded (MakeCheckFileResultsEmpty(filename, creationErrors)) | Some builder -> // Check the cache. We can only use cached results when there is no work to do to bring the background builder up-to-date - let cachedResults = bc.GetCachedCheckFileResult(builder, filename, sourceText, options) + let cachedResults = bc.GetCachedCheckFileResult(builder, filename, sourceText.GetHashCode(), options) match cachedResults with | Some (_, checkResults) -> return FSharpCheckFileAnswer.Succeeded checkResults @@ -2685,7 +2685,7 @@ type BackgroundCompiler(legacyReferenceResolver, projectCacheSize, keepAssemblyC } /// Parses and checks the source file and returns untyped AST and check results. - member bc.ParseAndCheckFileInProject (filename:string, fileVersion, sourceText, options:FSharpProjectOptions, textSnapshotInfo, userOpName) = + member bc.ParseAndCheckFileInProject (filename:string, fileVersion, sourceText: ISourceText, options:FSharpProjectOptions, textSnapshotInfo, userOpName) = let execWithReactorAsync action = reactor.EnqueueAndAwaitOpAsync(userOpName, "ParseAndCheckFileInProject", filename, action) async { try @@ -2706,7 +2706,7 @@ type BackgroundCompiler(legacyReferenceResolver, projectCacheSize, keepAssemblyC return (parseResults, FSharpCheckFileAnswer.Aborted) | Some builder -> - let cachedResults = bc.GetCachedCheckFileResult(builder, filename, sourceText, options) + let cachedResults = bc.GetCachedCheckFileResult(builder, filename, sourceText.GetHashCode(), options) match cachedResults with | Some (parseResults, checkResults) -> @@ -2772,7 +2772,7 @@ type BackgroundCompiler(legacyReferenceResolver, projectCacheSize, keepAssemblyC match sourceText with | Some sourceText -> parseCacheLock.AcquireLock (fun ltok -> - match checkFileInProjectCache.TryGet(ltok,(filename,sourceText,options)) with + match checkFileInProjectCache.TryGet(ltok,(filename,sourceText.GetHashCode(),options)) with | Some (a,b,c,_) -> Some (a,b,c) | None -> parseCacheLock.AcquireLock (fun ltok -> checkFileInProjectCachePossiblyStale.TryGet(ltok,(filename,options)))) | None -> parseCacheLock.AcquireLock (fun ltok -> checkFileInProjectCachePossiblyStale.TryGet(ltok,(filename,options))) @@ -3008,14 +3008,14 @@ type FSharpChecker(legacyReferenceResolver, projectCacheSize, keepAssemblyConten member ic.ReferenceResolver = legacyReferenceResolver - member ic.MatchBraces(filename, sourceText, options: FSharpParsingOptions, ?userOpName: string) = + member ic.MatchBraces(filename, sourceText: ISourceText, options: FSharpParsingOptions, ?userOpName: string) = let userOpName = defaultArg userOpName "Unknown" async { - match braceMatchCache.TryGet(AssumeAnyCallerThreadWithoutEvidence(), (filename, sourceText, options)) with + match braceMatchCache.TryGet(AssumeAnyCallerThreadWithoutEvidence(), (filename, sourceText.GetHashCode(), options)) with | Some res -> return res | None -> let res = Parser.matchBraces(sourceText, filename, options, userOpName) - braceMatchCache.Set(AssumeAnyCallerThreadWithoutEvidence(), (filename, sourceText, options), res) + braceMatchCache.Set(AssumeAnyCallerThreadWithoutEvidence(), (filename, sourceText.GetHashCode(), options), res) return res } diff --git a/src/utils/prim-lexing.fs b/src/utils/prim-lexing.fs index 87012685b3f..83a219a039d 100644 --- a/src/utils/prim-lexing.fs +++ b/src/utils/prim-lexing.fs @@ -27,6 +27,8 @@ type ISourceText = abstract CopyTo : sourceIndex: int * destination: char [] * destinationIndex: int * count: int -> unit + abstract GetHashCode : unit -> int + [] type StringText(str: string) = @@ -93,6 +95,8 @@ type StringText(str: string) = member __.CopyTo(sourceIndex, destination, destinationIndex, count) = str.CopyTo(sourceIndex, destination, destinationIndex, count) + member __.GetHashCode() = str.GetHashCode() + module SourceText = let ofString str = StringText(str) :> ISourceText diff --git a/src/utils/prim-lexing.fsi b/src/utils/prim-lexing.fsi index b968ba3605b..c1c9e986e8b 100644 --- a/src/utils/prim-lexing.fsi +++ b/src/utils/prim-lexing.fsi @@ -23,7 +23,9 @@ type ISourceText = abstract ContentEquals : sourceText: ISourceText -> bool - abstract CopyTo : sourceIndex: int * destination: char [] * destinationIndex: int * count: int -> unit + abstract CopyTo : sourceIndex: int * destination: char [] * destinationIndex: int * count: int -> unit + + abstract GetHashCode : unit -> int module SourceText = diff --git a/vsintegration/src/FSharp.Editor/Common/Extensions.fs b/vsintegration/src/FSharp.Editor/Common/Extensions.fs index 04245876425..425f815c575 100644 --- a/vsintegration/src/FSharp.Editor/Common/Extensions.fs +++ b/vsintegration/src/FSharp.Editor/Common/Extensions.fs @@ -46,6 +46,15 @@ module private SourceText = open System.Runtime.CompilerServices + /// Ported from Roslyn.Utilities + [] + module Hash = + /// (From Roslyn) This is how VB Anonymous Types combine hash values for fields. + let combine (newKey: int) (currentKey: int) = (currentKey * (int 0xA5555529)) + newKey + + let combineValues (values: seq<'T>) = + (0, values) ||> Seq.fold (fun hash value -> combine (value.GetHashCode()) hash) + let weakTable = ConditionalWeakTable() let create (sourceText: SourceText) = @@ -101,6 +110,16 @@ module private SourceText = member __.CopyTo(sourceIndex, destination, destinationIndex, count) = sourceText.CopyTo(sourceIndex, destination, destinationIndex, count) + + member __.GetHashCode() = + let checksum = sourceText.GetChecksum() + let contentsHash = if not checksum.IsDefault then Hash.combineValues checksum else 0 + let encodingHash = if not (isNull sourceText.Encoding) then sourceText.Encoding.GetHashCode() else 0 + + sourceText.ChecksumAlgorithm.GetHashCode() + |> Hash.combine encodingHash + |> Hash.combine contentsHash + |> Hash.combine sourceText.Length } sourceText From 39f9d10074daac349b62faad140aafbc51269079 Mon Sep 17 00:00:00 2001 From: Phillip Carter Date: Wed, 2 Jan 2019 13:26:33 -0800 Subject: [PATCH 2/5] Fix dumb thing I forgot --- src/fsharp/service/service.fs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/fsharp/service/service.fs b/src/fsharp/service/service.fs index 8664dc33018..e1bce590089 100644 --- a/src/fsharp/service/service.fs +++ b/src/fsharp/service/service.fs @@ -2629,7 +2629,7 @@ type BackgroundCompiler(legacyReferenceResolver, projectCacheSize, keepAssemblyC match incrementalBuildersCache.TryGetAny (ctok, options) with | Some (Some builder, creationErrors, _) -> - match bc.GetCachedCheckFileResult(builder, filename, sourceText.GetHashCode(), options) with + match bc.GetCachedCheckFileResult(builder, filename, sourceText, options) with | Some (_, checkResults) -> return Some (builder, creationErrors, Some (FSharpCheckFileAnswer.Succeeded checkResults)) | _ -> return Some (builder, creationErrors, None) | _ -> return None // the builder wasn't ready @@ -2669,7 +2669,7 @@ type BackgroundCompiler(legacyReferenceResolver, projectCacheSize, keepAssemblyC | None -> return FSharpCheckFileAnswer.Succeeded (MakeCheckFileResultsEmpty(filename, creationErrors)) | Some builder -> // Check the cache. We can only use cached results when there is no work to do to bring the background builder up-to-date - let cachedResults = bc.GetCachedCheckFileResult(builder, filename, sourceText.GetHashCode(), options) + let cachedResults = bc.GetCachedCheckFileResult(builder, filename, sourceText, options) match cachedResults with | Some (_, checkResults) -> return FSharpCheckFileAnswer.Succeeded checkResults @@ -2706,7 +2706,7 @@ type BackgroundCompiler(legacyReferenceResolver, projectCacheSize, keepAssemblyC return (parseResults, FSharpCheckFileAnswer.Aborted) | Some builder -> - let cachedResults = bc.GetCachedCheckFileResult(builder, filename, sourceText.GetHashCode(), options) + let cachedResults = bc.GetCachedCheckFileResult(builder, filename, sourceText, options) match cachedResults with | Some (parseResults, checkResults) -> From 6927e9c3c48a46db01d6a3514903ce593d51c7d3 Mon Sep 17 00:00:00 2001 From: Phillip Carter Date: Wed, 2 Jan 2019 14:26:04 -0800 Subject: [PATCH 3/5] I guess this is required ayyy lmao --- src/utils/prim-lexing.fs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/utils/prim-lexing.fs b/src/utils/prim-lexing.fs index 83a219a039d..0f2005e4d30 100644 --- a/src/utils/prim-lexing.fs +++ b/src/utils/prim-lexing.fs @@ -25,7 +25,7 @@ type ISourceText = abstract ContentEquals : sourceText: ISourceText -> bool - abstract CopyTo : sourceIndex: int * destination: char [] * destinationIndex: int * count: int -> unit + abstract CopyTo : sourceIndex: int * destination: char [] * destinationIndex: int * count: int -> unit abstract GetHashCode : unit -> int @@ -52,6 +52,9 @@ type StringText(str: string) = lazy getLines str member __.String = str + + override __.GetHashCode() = str.GetHashCode() + override __.Equals(obj: obj) = str.Equals(obj) interface ISourceText with @@ -95,7 +98,7 @@ type StringText(str: string) = member __.CopyTo(sourceIndex, destination, destinationIndex, count) = str.CopyTo(sourceIndex, destination, destinationIndex, count) - member __.GetHashCode() = str.GetHashCode() + member this.GetHashCode() = this.GetHashCode() module SourceText = From e11f7e8e5ce4007860a460b25a32875f6e39f98a Mon Sep 17 00:00:00 2001 From: Phillip Carter Date: Wed, 2 Jan 2019 14:55:12 -0800 Subject: [PATCH 4/5] Override HashCode instead and do a few less computations --- src/fsharp/service/service.fs | 10 +- src/utils/prim-lexing.fs | 4 - src/utils/prim-lexing.fsi | 2 - .../src/FSharp.Editor/Common/Extensions.fs | 99 ++++++++++--------- 4 files changed, 57 insertions(+), 58 deletions(-) diff --git a/src/fsharp/service/service.fs b/src/fsharp/service/service.fs index e1bce590089..3545293e44e 100644 --- a/src/fsharp/service/service.fs +++ b/src/fsharp/service/service.fs @@ -2511,13 +2511,14 @@ type BackgroundCompiler(legacyReferenceResolver, projectCacheSize, keepAssemblyC member bc.ParseFile(filename: string, sourceText: ISourceText, options: FSharpParsingOptions, userOpName: string) = async { - match parseCacheLock.AcquireLock(fun ltok -> parseFileCache.TryGet(ltok, (filename, sourceText.GetHashCode(), options))) with + let hash = sourceText.GetHashCode() + match parseCacheLock.AcquireLock(fun ltok -> parseFileCache.TryGet(ltok, (filename, hash, options))) with | Some res -> return res | None -> foregroundParseCount <- foregroundParseCount + 1 let parseErrors, parseTreeOpt, anyErrors = Parser.parseFile(sourceText, filename, options, userOpName) let res = FSharpParseFileResults(parseErrors, parseTreeOpt, anyErrors, options.SourceFiles) - parseCacheLock.AcquireLock(fun ltok -> parseFileCache.Set(ltok, (filename, sourceText.GetHashCode(), options), res)) + parseCacheLock.AcquireLock(fun ltok -> parseFileCache.Set(ltok, (filename, hash, options), res)) return res } @@ -3010,12 +3011,13 @@ type FSharpChecker(legacyReferenceResolver, projectCacheSize, keepAssemblyConten member ic.MatchBraces(filename, sourceText: ISourceText, options: FSharpParsingOptions, ?userOpName: string) = let userOpName = defaultArg userOpName "Unknown" + let hash = sourceText.GetHashCode() async { - match braceMatchCache.TryGet(AssumeAnyCallerThreadWithoutEvidence(), (filename, sourceText.GetHashCode(), options)) with + match braceMatchCache.TryGet(AssumeAnyCallerThreadWithoutEvidence(), (filename, hash, options)) with | Some res -> return res | None -> let res = Parser.matchBraces(sourceText, filename, options, userOpName) - braceMatchCache.Set(AssumeAnyCallerThreadWithoutEvidence(), (filename, sourceText.GetHashCode(), options), res) + braceMatchCache.Set(AssumeAnyCallerThreadWithoutEvidence(), (filename, hash, options), res) return res } diff --git a/src/utils/prim-lexing.fs b/src/utils/prim-lexing.fs index 0f2005e4d30..6b81e38c285 100644 --- a/src/utils/prim-lexing.fs +++ b/src/utils/prim-lexing.fs @@ -27,8 +27,6 @@ type ISourceText = abstract CopyTo : sourceIndex: int * destination: char [] * destinationIndex: int * count: int -> unit - abstract GetHashCode : unit -> int - [] type StringText(str: string) = @@ -98,8 +96,6 @@ type StringText(str: string) = member __.CopyTo(sourceIndex, destination, destinationIndex, count) = str.CopyTo(sourceIndex, destination, destinationIndex, count) - member this.GetHashCode() = this.GetHashCode() - module SourceText = let ofString str = StringText(str) :> ISourceText diff --git a/src/utils/prim-lexing.fsi b/src/utils/prim-lexing.fsi index c1c9e986e8b..0544adc3409 100644 --- a/src/utils/prim-lexing.fsi +++ b/src/utils/prim-lexing.fsi @@ -25,8 +25,6 @@ type ISourceText = abstract CopyTo : sourceIndex: int * destination: char [] * destinationIndex: int * count: int -> unit - abstract GetHashCode : unit -> int - module SourceText = val ofString : string -> ISourceText diff --git a/vsintegration/src/FSharp.Editor/Common/Extensions.fs b/vsintegration/src/FSharp.Editor/Common/Extensions.fs index 425f815c575..20979f5decc 100644 --- a/vsintegration/src/FSharp.Editor/Common/Extensions.fs +++ b/vsintegration/src/FSharp.Editor/Common/Extensions.fs @@ -59,67 +59,70 @@ module private SourceText = let create (sourceText: SourceText) = let sourceText = - { new ISourceText with + { + new Object() with + member __.ToString() = sourceText.ToString() + override __.GetHashCode() = + let checksum = sourceText.GetChecksum() + let contentsHash = if not checksum.IsDefault then Hash.combineValues checksum else 0 + let encodingHash = if not (isNull sourceText.Encoding) then sourceText.Encoding.GetHashCode() else 0 + + sourceText.ChecksumAlgorithm.GetHashCode() + |> Hash.combine encodingHash + |> Hash.combine contentsHash + |> Hash.combine sourceText.Length + + interface ISourceText with - member __.Item with get index = sourceText.[index] + member __.Item with get index = sourceText.[index] - member __.GetLineString(lineIndex) = - sourceText.Lines.[lineIndex].ToString() + member __.GetLineString(lineIndex) = + sourceText.Lines.[lineIndex].ToString() - member __.GetLineCount() = - sourceText.Lines.Count + member __.GetLineCount() = + sourceText.Lines.Count - member __.GetLastCharacterPosition() = - if sourceText.Lines.Count > 0 then - (sourceText.Lines.Count, sourceText.Lines.[sourceText.Lines.Count - 1].Span.Length) - else - (0, 0) - - member __.GetSubTextString(start, length) = - sourceText.GetSubText(TextSpan(start, length)).ToString() - - member __.SubTextEquals(target, startIndex) = - if startIndex < 0 || startIndex >= sourceText.Length then - invalidArg "startIndex" "Out of range." + member __.GetLastCharacterPosition() = + if sourceText.Lines.Count > 0 then + (sourceText.Lines.Count, sourceText.Lines.[sourceText.Lines.Count - 1].Span.Length) + else + (0, 0) - if String.IsNullOrEmpty(target) then - invalidArg "target" "Is null or empty." + member __.GetSubTextString(start, length) = + sourceText.GetSubText(TextSpan(start, length)).ToString() - let lastIndex = startIndex + target.Length - if lastIndex <= startIndex || lastIndex >= sourceText.Length then - invalidArg "target" "Too big." + member __.SubTextEquals(target, startIndex) = + if startIndex < 0 || startIndex >= sourceText.Length then + invalidArg "startIndex" "Out of range." - let mutable finished = false - let mutable didEqual = true - let mutable i = 0 - while not finished && i < target.Length do - if target.[i] <> sourceText.[startIndex + i] then - didEqual <- false - finished <- true // bail out early - else - i <- i + 1 + if String.IsNullOrEmpty(target) then + invalidArg "target" "Is null or empty." - didEqual + let lastIndex = startIndex + target.Length + if lastIndex <= startIndex || lastIndex >= sourceText.Length then + invalidArg "target" "Too big." - member __.ContentEquals(sourceText) = - match sourceText with - | :? SourceText as sourceText -> sourceText.ContentEquals(sourceText) - | _ -> false + let mutable finished = false + let mutable didEqual = true + let mutable i = 0 + while not finished && i < target.Length do + if target.[i] <> sourceText.[startIndex + i] then + didEqual <- false + finished <- true // bail out early + else + i <- i + 1 - member __.Length = sourceText.Length + didEqual - member __.CopyTo(sourceIndex, destination, destinationIndex, count) = - sourceText.CopyTo(sourceIndex, destination, destinationIndex, count) + member __.ContentEquals(sourceText) = + match sourceText with + | :? SourceText as sourceText -> sourceText.ContentEquals(sourceText) + | _ -> false - member __.GetHashCode() = - let checksum = sourceText.GetChecksum() - let contentsHash = if not checksum.IsDefault then Hash.combineValues checksum else 0 - let encodingHash = if not (isNull sourceText.Encoding) then sourceText.Encoding.GetHashCode() else 0 + member __.Length = sourceText.Length - sourceText.ChecksumAlgorithm.GetHashCode() - |> Hash.combine encodingHash - |> Hash.combine contentsHash - |> Hash.combine sourceText.Length + member __.CopyTo(sourceIndex, destination, destinationIndex, count) = + sourceText.CopyTo(sourceIndex, destination, destinationIndex, count) } sourceText From 6b882eb3365299d60625d4049e041f4369c6d04e Mon Sep 17 00:00:00 2001 From: Phillip Carter Date: Wed, 2 Jan 2019 15:04:55 -0800 Subject: [PATCH 5/5] Remove ToString in obj expression --- vsintegration/src/FSharp.Editor/Common/Extensions.fs | 1 - 1 file changed, 1 deletion(-) diff --git a/vsintegration/src/FSharp.Editor/Common/Extensions.fs b/vsintegration/src/FSharp.Editor/Common/Extensions.fs index 20979f5decc..159327274f3 100644 --- a/vsintegration/src/FSharp.Editor/Common/Extensions.fs +++ b/vsintegration/src/FSharp.Editor/Common/Extensions.fs @@ -61,7 +61,6 @@ module private SourceText = let sourceText = { new Object() with - member __.ToString() = sourceText.ToString() override __.GetHashCode() = let checksum = sourceText.GetChecksum() let contentsHash = if not checksum.IsDefault then Hash.combineValues checksum else 0