From 6bb2c079891c929fcd1149af22bf2089558059fc Mon Sep 17 00:00:00 2001 From: Will Smith Date: Tue, 6 Oct 2020 15:53:31 -0700 Subject: [PATCH] [Tooling Optimization] Skip background type-checking on implementation files if signature files exist (#10199) * Initial work to enable FSI optimizations in tooling * Remove extra type-check function * Not working but initial work to lazily eval * some work * simplify * simplify again * Using Async * work * work * builds * almost there * Minor change * minor refactor * working, but not quite optimized * Fix dependency files * Refactor types * Added 'enableLazyTypeChecking'. * Enable lazy type checking in VS * Added documentation * Added SyntaxTreeAccumulator * prevent parsing impl files if possible * Trying to fix tests * Refactor * Throw if enablePartialChecking and keepAssemblyContents are both enabled * minor comment update * minor refactor * Trying to pass tests * Only getting assembly data * Remove unnecessary change * Added some documentation * Minor comment update * Renamed quickCheck to skipImplIfSigExists * Minor update * Feedback --- src/fsharp/ParseAndCheckInputs.fs | 25 +- src/fsharp/ParseAndCheckInputs.fsi | 3 +- src/fsharp/service/IncrementalBuild.fs | 721 ++++++++++++------ src/fsharp/service/IncrementalBuild.fsi | 85 ++- src/fsharp/service/service.fs | 240 +++--- src/fsharp/service/service.fsi | 7 +- .../SurfaceArea.netstandard.fs | 2 +- .../LanguageService/FSharpCheckerProvider.fs | 3 +- 8 files changed, 722 insertions(+), 364 deletions(-) diff --git a/src/fsharp/ParseAndCheckInputs.fs b/src/fsharp/ParseAndCheckInputs.fs index 2021776917a..5c31ae52f5e 100644 --- a/src/fsharp/ParseAndCheckInputs.fs +++ b/src/fsharp/ParseAndCheckInputs.fs @@ -576,7 +576,7 @@ type TcState = member x.NextStateAfterIncrementalFragment tcEnvAtEndOfLastInput = { x with tcsTcSigEnv = tcEnvAtEndOfLastInput - tcsTcImplEnv = tcEnvAtEndOfLastInput } + tcsTcImplEnv = tcEnvAtEndOfLastInput } /// Create the initial type checking state for compiling an assembly @@ -621,7 +621,7 @@ let GetInitialTcState(m, ccuName, tcConfig: TcConfig, tcGlobals, tcImports: TcIm tcsCcuSig = Construct.NewEmptyModuleOrNamespaceType Namespace } /// Typecheck a single file (or interactive entry into F# Interactive) -let TypeCheckOneInputEventually (checkForErrors, tcConfig: TcConfig, tcImports: TcImports, tcGlobals, prefixPathOpt, tcSink, tcState: TcState, inp: ParsedInput) = +let TypeCheckOneInputEventually (checkForErrors, tcConfig: TcConfig, tcImports: TcImports, tcGlobals, prefixPathOpt, tcSink, tcState: TcState, inp: ParsedInput, skipImplIfSigExists: bool) = eventually { try @@ -686,11 +686,21 @@ let TypeCheckOneInputEventually (checkForErrors, tcConfig: TcConfig, tcImports: let conditionalDefines = if tcConfig.noConditionalErasure then None else Some (tcConfig.conditionalCompilationDefines) + let hadSig = rootSigOpt.IsSome + // Typecheck the implementation file - let! topAttrs, implFile, _implFileHiddenType, tcEnvAtEnd, createsGeneratedProvidedTypes = - TypeCheckOneImplFile (tcGlobals, tcState.tcsNiceNameGen, amap, tcState.tcsCcu, checkForErrors, conditionalDefines, tcSink, tcConfig.internalTestSpanStackReferring) tcImplEnv rootSigOpt file + let typeCheckOne = + if skipImplIfSigExists && hadSig then + let dummyExpr = ModuleOrNamespaceExprWithSig.ModuleOrNamespaceExprWithSig(rootSigOpt.Value, ModuleOrNamespaceExpr.TMDefs [], range.Zero) + let dummyImplFile = TypedImplFile.TImplFile(qualNameOfFile, [], dummyExpr, false, false, StampMap []) + + (EmptyTopAttrs, dummyImplFile, Unchecked.defaultof<_>, tcImplEnv, false) + |> Eventually.Done + else + TypeCheckOneImplFile (tcGlobals, tcState.tcsNiceNameGen, amap, tcState.tcsCcu, checkForErrors, conditionalDefines, tcSink, tcConfig.internalTestSpanStackReferring) tcImplEnv rootSigOpt file + + let! topAttrs, implFile, _implFileHiddenType, tcEnvAtEnd, createsGeneratedProvidedTypes = typeCheckOne - let hadSig = rootSigOpt.IsSome let implFileSigType = SigTypeOfImplFile implFile let rootImpls = Zset.add qualNameOfFile tcState.tcsRootImpls @@ -741,7 +751,7 @@ let TypeCheckOneInput (ctok, checkForErrors, tcConfig, tcImports, tcGlobals, pre // 'use' ensures that the warning handler is restored at the end use unwindEL = PushErrorLoggerPhaseUntilUnwind(fun oldLogger -> GetErrorLoggerFilteringByScopedPragmas(false, GetScopedPragmasForInput inp, oldLogger) ) use unwindBP = PushThreadBuildPhaseUntilUnwind BuildPhase.TypeCheck - TypeCheckOneInputEventually (checkForErrors, tcConfig, tcImports, tcGlobals, prefixPathOpt, TcResultsSink.NoSink, tcState, inp) + TypeCheckOneInputEventually (checkForErrors, tcConfig, tcImports, tcGlobals, prefixPathOpt, TcResultsSink.NoSink, tcState, inp, false) |> Eventually.force ctok /// Finish checking multiple files (or one interactive entry into F# Interactive) @@ -756,7 +766,7 @@ let TypeCheckMultipleInputsFinish(results, tcState: TcState) = let TypeCheckOneInputAndFinishEventually(checkForErrors, tcConfig: TcConfig, tcImports, tcGlobals, prefixPathOpt, tcSink, tcState, input) = eventually { Logger.LogBlockStart LogCompilerFunctionId.CompileOps_TypeCheckOneInputAndFinishEventually - let! results, tcState = TypeCheckOneInputEventually(checkForErrors, tcConfig, tcImports, tcGlobals, prefixPathOpt, tcSink, tcState, input) + let! results, tcState = TypeCheckOneInputEventually(checkForErrors, tcConfig, tcImports, tcGlobals, prefixPathOpt, tcSink, tcState, input, false) let result = TypeCheckMultipleInputsFinish([results], tcState) Logger.LogBlockStop LogCompilerFunctionId.CompileOps_TypeCheckOneInputAndFinishEventually return result @@ -779,4 +789,3 @@ let TypeCheckClosedInputSet (ctok, checkForErrors, tcConfig, tcImports, tcGlobal let (tcEnvAtEndOfLastFile, topAttrs, implFiles, _), tcState = TypeCheckMultipleInputsFinish(results, tcState) let tcState, declaredImpls = TypeCheckClosedInputSetFinish (implFiles, tcState) tcState, topAttrs, declaredImpls, tcEnvAtEndOfLastFile - diff --git a/src/fsharp/ParseAndCheckInputs.fsi b/src/fsharp/ParseAndCheckInputs.fsi index ba02cb05a96..4e8a9ace2ab 100644 --- a/src/fsharp/ParseAndCheckInputs.fsi +++ b/src/fsharp/ParseAndCheckInputs.fsi @@ -87,7 +87,8 @@ val TypeCheckOneInputEventually : LongIdent option * NameResolution.TcResultsSink * TcState * - ParsedInput + ParsedInput * + skipImplIfSigExists: bool -> Eventually<(TcEnv * TopAttribs * TypedImplFile option * ModuleOrNamespaceType) * TcState> /// Finish the checking of multiple inputs diff --git a/src/fsharp/service/IncrementalBuild.fs b/src/fsharp/service/IncrementalBuild.fs index 4004b4e456f..ad7ae10b31b 100755 --- a/src/fsharp/service/IncrementalBuild.fs +++ b/src/fsharp/service/IncrementalBuild.fs @@ -31,6 +31,7 @@ open FSharp.Compiler.TypedTreeOps open Microsoft.DotNet.DependencyManager +open Internal.Utilities open Internal.Utilities.Collections [] @@ -1022,16 +1023,93 @@ module IncrementalBuilderEventTesting = module Tc = FSharp.Compiler.TypeChecker +// This module is only here to contain the SyntaxTree type as to avoid amiguity with the module FSharp.Compiler.SyntaxTree. +[] +module IncrementalBuildSyntaxTree = -/// Accumulated results of type checking. + /// Information needed to lazily parse a file to get a ParsedInput. Internally uses a weak cache. + [] + type SyntaxTree (tcConfig: TcConfig, fileParsed: Event, lexResourceManager, sourceRange: range, filename: string, isLastCompiland) = + + let mutable weakCache: WeakReference<_> option = None + + let parse(sigNameOpt: SyntaxTree.QualifiedNameOfFile option) = + let errorLogger = CompilationErrorLogger("Parse", tcConfig.errorSeverityOptions) + // Return the disposable object that cleans up + use _holder = new CompilationGlobalsScope(errorLogger, BuildPhase.Parse) + + try + IncrementalBuilderEventTesting.MRU.Add(IncrementalBuilderEventTesting.IBEParsed filename) + let lower = String.lowercase filename + let canSkip = sigNameOpt.IsSome && FSharpImplFileSuffixes |> List.exists (Filename.checkSuffix lower) + let input = + if canSkip then + SyntaxTree.ParsedInput.ImplFile( + SyntaxTree.ParsedImplFileInput( + filename, + false, + sigNameOpt.Value, + [], + [], + [], + isLastCompiland + ) + ) |> Some + else + ParseOneInputFile(tcConfig, lexResourceManager, [], filename, isLastCompiland, errorLogger, (*retryLocked*)true) + + fileParsed.Trigger filename + + let res = input, sourceRange, filename, errorLogger.GetErrors () + // If we do not skip parsing the file, then we can cache the real result. + if not canSkip then + weakCache <- Some(WeakReference<_>(res)) + res + with exn -> + let msg = sprintf "unexpected failure in SyntaxTree.parse\nerror = %s" (exn.ToString()) + System.Diagnostics.Debug.Assert(false, msg) + failwith msg + + /// Parse the given file and return the given input. + member _.Parse sigNameOpt = + match weakCache with + | Some weakCache -> + match weakCache.TryGetTarget() with + | true, res -> res + | _ -> parse sigNameOpt + | _ -> parse sigNameOpt + + member _.FileName = filename + +/// Accumulated results of type checking. The minimum amount of state in order to continue type-checking following files. [] -type TypeCheckAccumulator = - { tcState: TcState - tcImports: TcImports - tcGlobals: TcGlobals - tcConfig: TcConfig - tcEnvAtEndOfFile: TcEnv +type TcInfo = + { + tcState: TcState + tcEnvAtEndOfFile: TcEnv + + /// Disambiguation table for module names + moduleNamesDict: ModuleNamesDict + + topAttribs: TopAttribs option + latestCcuSigForFile: ModuleOrNamespaceType option + + /// Accumulated errors, last file first + tcErrorsRev:(PhasedDiagnostic * FSharpErrorSeverity)[] list + + tcDependencyFiles: string list + + sigNameOpt: (string * SyntaxTree.QualifiedNameOfFile) option + } + + member x.TcErrors = + Array.concat (List.rev x.tcErrorsRev) + +/// Accumulated results of type checking. Optional data that isn't needed to type-check a file, but needed for more information for in tooling. +[] +type TcInfoOptional = + { /// Accumulated resolutions, last file first tcResolutionsRev: TcResolutions list @@ -1041,27 +1119,323 @@ type TypeCheckAccumulator = /// Accumulated 'open' declarations, last file first tcOpenDeclarationsRev: OpenDeclaration[] list - topAttribs: TopAttribs option - /// Result of checking most recent file, if any latestImplFile: TypedImplFile option - - latestCcuSigForFile: ModuleOrNamespaceType option - - tcDependencyFiles: string list - - /// Disambiguation table for module names - tcModuleNamesDict: ModuleNamesDict - - /// Accumulated errors, last file first - tcErrorsRev:(PhasedDiagnostic * FSharpErrorSeverity)[] list /// If enabled, stores a linear list of ranges and strings that identify an Item(symbol) in a file. Used for background find all references. itemKeyStore: ItemKeyStore option /// If enabled, holds semantic classification information for Item(symbol)s in a file. - semanticClassification: struct (range * SemanticClassificationType) [] } + semanticClassification: struct (range * SemanticClassificationType) [] + } + + member x.TcSymbolUses = + List.rev x.tcSymbolUsesRev + +/// Accumulated results of type checking. +[] +type TcInfoState = + | PartialState of TcInfo + | FullState of TcInfo * TcInfoOptional + + member this.Partial = + match this with + | PartialState tcInfo -> tcInfo + | FullState(tcInfo, _) -> tcInfo + +/// Semantic model of an underlying syntax tree. +[] +type SemanticModel private (tcConfig: TcConfig, + tcGlobals: TcGlobals, + tcImports: TcImports, + keepAssemblyContents, keepAllBackgroundResolutions, + maxTimeShareMilliseconds, keepAllBackgroundSymbolUses, + enableBackgroundItemKeyStoreAndSemanticClassification, + enablePartialTypeChecking, + beforeFileChecked: Event, + fileChecked: Event, + prevTcInfo: TcInfo, + prevTcInfoOptional: Eventually, + syntaxTreeOpt: SyntaxTree option, + lazyTcInfoState: TcInfoState option ref) = + + let defaultTypeCheck () = + eventually { + match prevTcInfoOptional with + | Eventually.Done(Some prevTcInfoOptional) -> + return FullState(prevTcInfo, prevTcInfoOptional) + | _ -> + return PartialState prevTcInfo + } + + member _.TcConfig = tcConfig + + member _.TcGlobals = tcGlobals + member _.TcImports = tcImports + + member this.GetState(partialCheck: bool) = + let partialCheck = + // Only partial check if we have enabled it. + if enablePartialTypeChecking then partialCheck + else false + + let mustCheck = + match !lazyTcInfoState, partialCheck with + | None, _ -> true + | Some(PartialState _), false -> true + | _ -> false + + if mustCheck then + lazyTcInfoState := None + + match !lazyTcInfoState with + | Some tcInfoState -> tcInfoState |> Eventually.Done + | _ -> + eventually { + let! tcInfoState = this.TypeCheck(partialCheck) + lazyTcInfoState := Some tcInfoState + return tcInfoState + } + + member this.Next(syntaxTree) = + eventually { + let! prevState = this.GetState(true) + let lazyPrevTcInfoOptional = + eventually { + let! prevState = this.GetState(false) + match prevState with + | FullState(_, prevTcInfoOptional) -> return Some prevTcInfoOptional + | _ -> return None + } + return + SemanticModel( + tcConfig, + tcGlobals, + tcImports, + keepAssemblyContents, + keepAllBackgroundResolutions, + maxTimeShareMilliseconds, + keepAllBackgroundSymbolUses, + enableBackgroundItemKeyStoreAndSemanticClassification, + enablePartialTypeChecking, + beforeFileChecked, + fileChecked, + prevState.Partial, + lazyPrevTcInfoOptional, + Some syntaxTree, + ref None) + } + + member this.Finish(finalTcErrorsRev, finalTopAttribs) = + eventually { + let! state = this.GetState(true) + + let finishTcInfo = { state.Partial with tcErrorsRev = finalTcErrorsRev; topAttribs = finalTopAttribs } + let finishState = + match state with + | PartialState(_) -> PartialState(finishTcInfo) + | FullState(_, tcInfoOptional) -> FullState(finishTcInfo, tcInfoOptional) + + return + SemanticModel( + tcConfig, + tcGlobals, + tcImports, + keepAssemblyContents, + keepAllBackgroundResolutions, + maxTimeShareMilliseconds, + keepAllBackgroundSymbolUses, + enableBackgroundItemKeyStoreAndSemanticClassification, + enablePartialTypeChecking, + beforeFileChecked, + fileChecked, + prevTcInfo, + prevTcInfoOptional, + syntaxTreeOpt, + ref (Some finishState)) + } + + member this.TcInfo = + eventually { + let! state = this.GetState(true) + return state.Partial + } + + member this.TcInfoWithOptional = + eventually { + let! state = this.GetState(false) + match state with + | FullState(tcInfo, tcInfoOptional) -> return tcInfo, tcInfoOptional + | PartialState tcInfo -> + return + tcInfo, + { + tcResolutionsRev = [] + tcSymbolUsesRev = [] + tcOpenDeclarationsRev = [] + latestImplFile = None + itemKeyStore = None + semanticClassification = [||] + } + } + + member private _.TypeCheck (partialCheck: bool) = + match partialCheck, !lazyTcInfoState with + | true, Some (PartialState _ as state) + | true, Some (FullState _ as state) -> state |> Eventually.Done + | false, Some (FullState _ as state) -> state |> Eventually.Done + | _ -> + + eventually { + match syntaxTreeOpt with + | None -> return! defaultTypeCheck () + | Some syntaxTree -> + let sigNameOpt = + if partialCheck then + let sigFileName = Path.ChangeExtension(syntaxTree.FileName, ".fsi") + match prevTcInfo.sigNameOpt with + | Some (expectedSigFileName, sigName) when String.Equals(expectedSigFileName, sigFileName, StringComparison.OrdinalIgnoreCase) -> + Some sigName + | _ -> + None + else + None + match syntaxTree.Parse sigNameOpt with + | Some input, _sourceRange, filename, parseErrors -> + IncrementalBuilderEventTesting.MRU.Add(IncrementalBuilderEventTesting.IBETypechecked filename) + let capturingErrorLogger = CompilationErrorLogger("TypeCheck", tcConfig.errorSeverityOptions) + let errorLogger = GetErrorLoggerFilteringByScopedPragmas(false, GetScopedPragmasForInput input, capturingErrorLogger) + let fullComputation = + eventually { + beforeFileChecked.Trigger filename + let prevModuleNamesDict = prevTcInfo.moduleNamesDict + let prevTcState = prevTcInfo.tcState + let prevTcErrorsRev = prevTcInfo.tcErrorsRev + let prevTcDependencyFiles = prevTcInfo.tcDependencyFiles + + ApplyMetaCommandsFromInputToTcConfig (tcConfig, input, Path.GetDirectoryName filename, tcImports.DependencyProvider) |> ignore + let sink = TcResultsSinkImpl(tcGlobals) + let hadParseErrors = not (Array.isEmpty parseErrors) + let input, moduleNamesDict = DeduplicateParsedInputModuleName prevModuleNamesDict input + + Logger.LogBlockMessageStart filename LogCompilerFunctionId.IncrementalBuild_TypeCheck + let! (tcEnvAtEndOfFile, topAttribs, implFile, ccuSigForFile), tcState = + TypeCheckOneInputEventually + ((fun () -> hadParseErrors || errorLogger.ErrorCount > 0), + tcConfig, tcImports, + tcGlobals, + None, + (if partialCheck then TcResultsSink.NoSink else TcResultsSink.WithSink sink), + prevTcState, input, + partialCheck) + Logger.LogBlockMessageStop filename LogCompilerFunctionId.IncrementalBuild_TypeCheck + + fileChecked.Trigger filename + let newErrors = Array.append parseErrors (capturingErrorLogger.GetErrors()) + + let tcEnvAtEndOfFile = if keepAllBackgroundResolutions then tcEnvAtEndOfFile else tcState.TcEnvFromImpls + + let tcInfo = + { + tcState = tcState + tcEnvAtEndOfFile = tcEnvAtEndOfFile + moduleNamesDict = moduleNamesDict + latestCcuSigForFile = Some ccuSigForFile + tcErrorsRev = newErrors :: prevTcErrorsRev + topAttribs = Some topAttribs + tcDependencyFiles = filename :: prevTcDependencyFiles + sigNameOpt = + match input with + | SyntaxTree.ParsedInput.SigFile(SyntaxTree.ParsedSigFileInput(fileName=fileName;qualifiedNameOfFile=qualName)) -> + Some(fileName, qualName) + | _ -> + None + } + + if partialCheck then + return PartialState tcInfo + else + match! prevTcInfoOptional with + | None -> return PartialState tcInfo + | Some prevTcInfoOptional -> + // Build symbol keys + let itemKeyStore, semanticClassification = + if enableBackgroundItemKeyStoreAndSemanticClassification then + Logger.LogBlockMessageStart filename LogCompilerFunctionId.IncrementalBuild_CreateItemKeyStoreAndSemanticClassification + let sResolutions = sink.GetResolutions() + let builder = ItemKeyStoreBuilder() + let preventDuplicates = HashSet({ new IEqualityComparer with + member _.Equals((s1, e1): struct(pos * pos), (s2, e2): struct(pos * pos)) = Range.posEq s1 s2 && Range.posEq e1 e2 + member _.GetHashCode o = o.GetHashCode() }) + sResolutions.CapturedNameResolutions + |> Seq.iter (fun cnr -> + let r = cnr.Range + if preventDuplicates.Add struct(r.Start, r.End) then + builder.Write(cnr.Range, cnr.Item)) + + let res = builder.TryBuildAndReset(), sResolutions.GetSemanticClassification(tcGlobals, tcImports.GetImportMap(), sink.GetFormatSpecifierLocations(), None) + Logger.LogBlockMessageStop filename LogCompilerFunctionId.IncrementalBuild_CreateItemKeyStoreAndSemanticClassification + res + else + None, [||] + + let tcInfoOptional = + { + /// Only keep the typed interface files when doing a "full" build for fsc.exe, otherwise just throw them away + latestImplFile = if keepAssemblyContents then implFile else None + tcResolutionsRev = (if keepAllBackgroundResolutions then sink.GetResolutions() else TcResolutions.Empty) :: prevTcInfoOptional.tcResolutionsRev + tcSymbolUsesRev = (if keepAllBackgroundSymbolUses then sink.GetSymbolUses() else TcSymbolUses.Empty) :: prevTcInfoOptional.tcSymbolUsesRev + tcOpenDeclarationsRev = sink.GetOpenDeclarations() :: prevTcInfoOptional.tcOpenDeclarationsRev + itemKeyStore = itemKeyStore + semanticClassification = semanticClassification + } + + return FullState(tcInfo, tcInfoOptional) + + } + + // Run part of the Eventually<_> computation until a timeout is reached. If not complete, + // return a new Eventually<_> computation which recursively runs more of the computation. + // - When the whole thing is finished commit the error results sent through the errorLogger. + // - Each time we do real work we reinstall the CompilationGlobalsScope + let timeSlicedComputation = + fullComputation |> + Eventually.repeatedlyProgressUntilDoneOrTimeShareOverOrCanceled + maxTimeShareMilliseconds + CancellationToken.None + (fun ctok f -> + // Reinstall the compilation globals each time we start or restart + use unwind = new CompilationGlobalsScope (errorLogger, BuildPhase.TypeCheck) + f ctok) + return! timeSlicedComputation + | _ -> + return! defaultTypeCheck () + } + + static member Create(tcConfig: TcConfig, + tcGlobals: TcGlobals, + tcImports: TcImports, + keepAssemblyContents, keepAllBackgroundResolutions, + maxTimeShareMilliseconds, keepAllBackgroundSymbolUses, + enableBackgroundItemKeyStoreAndSemanticClassification, + enablePartialTypeChecking, + beforeFileChecked: Event, + fileChecked: Event, + prevTcInfo: TcInfo, + prevTcInfoOptional: Eventually, + syntaxTreeOpt: SyntaxTree option) = + SemanticModel(tcConfig, tcGlobals, tcImports, + keepAssemblyContents, keepAllBackgroundResolutions, + maxTimeShareMilliseconds, keepAllBackgroundSymbolUses, + enableBackgroundItemKeyStoreAndSemanticClassification, + enablePartialTypeChecking, + beforeFileChecked, + fileChecked, + prevTcInfo, + prevTcInfoOptional, + syntaxTreeOpt, + ref None) /// Global service state type FrameworkImportsCacheKey = (*resolvedpath*)string list * string * (*TargetFrameworkDirectories*)string list * (*fsharpBinaries*)string * (*langVersion*)decimal @@ -1123,64 +1497,26 @@ type FrameworkImportsCache(keepStrongly) = /// Represents the interim state of checking an assembly -type PartialCheckResults = - { TcState: TcState - TcImports: TcImports - TcGlobals: TcGlobals - TcConfig: TcConfig - TcEnvAtEnd: TcEnv - - /// Kept in a stack so that each incremental update shares storage with previous files - TcErrorsRev: (PhasedDiagnostic * FSharpErrorSeverity)[] list - - /// Kept in a stack so that each incremental update shares storage with previous files - TcResolutionsRev: TcResolutions list - - /// Kept in a stack so that each incremental update shares storage with previous files - TcSymbolUsesRev: TcSymbolUses list +[] +type PartialCheckResults private (semanticModel: SemanticModel, timeStamp: DateTime) = - /// Kept in a stack so that each incremental update shares storage with previous files - TcOpenDeclarationsRev: OpenDeclaration[] list + let eval ctok (work: Eventually<'T>) = + match work with + | Eventually.Done res -> res + | _ -> Eventually.force ctok work - /// Disambiguation table for module names - ModuleNamesDict: ModuleNamesDict + member _.TcImports = semanticModel.TcImports + member _.TcGlobals = semanticModel.TcGlobals + member _.TcConfig = semanticModel.TcConfig - TcDependencyFiles: string list + member _.TimeStamp = timeStamp - TopAttribs: TopAttribs option + member _.TcInfo ctok = semanticModel.TcInfo |> eval ctok - TimeStamp: DateTime - - LatestImplementationFile: TypedImplFile option - - LatestCcuSigForFile: ModuleOrNamespaceType option - - ItemKeyStore: ItemKeyStore option - - SemanticClassification: struct (range * SemanticClassificationType) [] } - - member x.TcErrors = Array.concat (List.rev x.TcErrorsRev) - member x.TcSymbolUses = List.rev x.TcSymbolUsesRev - - static member Create (tcAcc: TypeCheckAccumulator, timestamp) = - { TcState = tcAcc.tcState - TcImports = tcAcc.tcImports - TcGlobals = tcAcc.tcGlobals - TcConfig = tcAcc.tcConfig - TcEnvAtEnd = tcAcc.tcEnvAtEndOfFile - TcErrorsRev = tcAcc.tcErrorsRev - TcResolutionsRev = tcAcc.tcResolutionsRev - TcSymbolUsesRev = tcAcc.tcSymbolUsesRev - TcOpenDeclarationsRev = tcAcc.tcOpenDeclarationsRev - TcDependencyFiles = tcAcc.tcDependencyFiles - TopAttribs = tcAcc.topAttribs - ModuleNamesDict = tcAcc.tcModuleNamesDict - TimeStamp = timestamp - LatestImplementationFile = tcAcc.latestImplFile - LatestCcuSigForFile = tcAcc.latestCcuSigForFile - ItemKeyStore = tcAcc.itemKeyStore - SemanticClassification = tcAcc.semanticClassification } + member _.TcInfoWithOptional ctok = semanticModel.TcInfoWithOptional |> eval ctok + static member Create (semanticModel: SemanticModel, timestamp) = + PartialCheckResults(semanticModel, timestamp) [] module Utilities = @@ -1222,7 +1558,6 @@ type RawFSharpAssemblyDataBackedByLanguageService (tcConfig, tcGlobals, tcState: member __.HasAnyFSharpSignatureDataAttribute = true member __.HasMatchingFSharpSignatureDataAttribute _ilg = true - /// Manages an incremental build graph for the build of a single F# project type IncrementalBuilder(tcGlobals, frameworkTcImports, nonFrameworkAssemblyInputs, nonFrameworkResolutions, unresolvedReferences, tcConfig: TcConfig, projectDirectory, outfile, assemblyName, niceNameGen: NiceNameGenerator, lexResourceManager, @@ -1230,6 +1565,7 @@ type IncrementalBuilder(tcGlobals, frameworkTcImports, nonFrameworkAssemblyInput keepAssemblyContents, keepAllBackgroundResolutions, maxTimeShareMilliseconds, keepAllBackgroundSymbolUses, enableBackgroundItemKeyStoreAndSemanticClassification, + enablePartialTypeChecking, dependencyProviderOpt: DependencyProvider option) = let tcConfigP = TcConfigProvider.Constant tcConfig @@ -1288,22 +1624,7 @@ type IncrementalBuilder(tcGlobals, frameworkTcImports, nonFrameworkAssemblyInput /// Parse the given file and return the given input. let ParseTask ctok (sourceRange: range, filename: string, isLastCompiland) = DoesNotRequireCompilerThreadTokenAndCouldPossiblyBeMadeConcurrent ctok - - let errorLogger = CompilationErrorLogger("ParseTask", tcConfig.errorSeverityOptions) - // Return the disposable object that cleans up - use _holder = new CompilationGlobalsScope(errorLogger, BuildPhase.Parse) - - try - IncrementalBuilderEventTesting.MRU.Add(IncrementalBuilderEventTesting.IBEParsed filename) - let input = ParseOneInputFile(tcConfig, lexResourceManager, [], filename, isLastCompiland, errorLogger, (*retryLocked*)true) - fileParsed.Trigger filename - - input, sourceRange, filename, errorLogger.GetErrors () - with exn -> - let msg = sprintf "unexpected failure in IncrementalFSharpBuild.Parse\nerror = %s" (exn.ToString()) - System.Diagnostics.Debug.Assert(false, msg) - failwith msg - + SyntaxTree(tcConfig, fileParsed, lexResourceManager, sourceRange, filename, isLastCompiland) /// This is a build task function that gets placed into the build rules as the computation for a Vector.Stamp /// @@ -1315,7 +1636,7 @@ type IncrementalBuilder(tcGlobals, frameworkTcImports, nonFrameworkAssemblyInput /// This is a build task function that gets placed into the build rules as the computation for a Vector.Demultiplex /// // Link all the assemblies together and produce the input typecheck accumulator - let CombineImportedAssembliesTask ctok _ : Cancellable = + let CombineImportedAssembliesTask ctok _ : Cancellable = cancellable { let errorLogger = CompilationErrorLogger("CombineImportedAssembliesTask", tcConfig.errorSeverityOptions) // Return the disposable object that cleans up @@ -1366,124 +1687,56 @@ type IncrementalBuilder(tcGlobals, frameworkTcImports, nonFrameworkAssemblyInput yield err, (if isError then FSharpErrorSeverity.Error else FSharpErrorSeverity.Warning) ] let initialErrors = Array.append (Array.ofList loadClosureErrors) (errorLogger.GetErrors()) - let tcAcc = - { tcGlobals=tcGlobals - tcImports=tcImports + let tcInfo = + { tcState=tcState - tcConfig=tcConfig tcEnvAtEndOfFile=tcInitial - tcResolutionsRev=[] - tcSymbolUsesRev=[] - tcOpenDeclarationsRev=[] topAttribs=None - latestImplFile=None latestCcuSigForFile=None - tcDependencyFiles=basicDependencies tcErrorsRev = [ initialErrors ] - tcModuleNamesDict = Map.empty - itemKeyStore = None - semanticClassification = [||] } - return tcAcc } + moduleNamesDict = Map.empty + tcDependencyFiles = basicDependencies + sigNameOpt = None + } + let tcInfoOptional = + { + tcResolutionsRev=[] + tcSymbolUsesRev=[] + tcOpenDeclarationsRev=[] + latestImplFile=None + itemKeyStore = None + semanticClassification = [||] + } + return + SemanticModel.Create( + tcConfig, + tcGlobals, + tcImports, + keepAssemblyContents, + keepAllBackgroundResolutions, + maxTimeShareMilliseconds, + keepAllBackgroundSymbolUses, + enableBackgroundItemKeyStoreAndSemanticClassification, + enablePartialTypeChecking, + beforeFileChecked, fileChecked, tcInfo, Eventually.Done (Some tcInfoOptional), None) } /// This is a build task function that gets placed into the build rules as the computation for a Vector.ScanLeft /// /// Type check all files. - let TypeCheckTask ctok (tcAcc: TypeCheckAccumulator) input: Eventually = - match input with - | Some input, _sourceRange, filename, parseErrors-> - IncrementalBuilderEventTesting.MRU.Add(IncrementalBuilderEventTesting.IBETypechecked filename) - let capturingErrorLogger = CompilationErrorLogger("TypeCheckTask", tcConfig.errorSeverityOptions) - let errorLogger = GetErrorLoggerFilteringByScopedPragmas(false, GetScopedPragmasForInput input, capturingErrorLogger) - let fullComputation = - eventually { - beforeFileChecked.Trigger filename - - ApplyMetaCommandsFromInputToTcConfig (tcConfig, input, Path.GetDirectoryName filename, tcAcc.tcImports.DependencyProvider) |> ignore - let sink = TcResultsSinkImpl(tcAcc.tcGlobals) - let hadParseErrors = not (Array.isEmpty parseErrors) - - let input, moduleNamesDict = DeduplicateParsedInputModuleName tcAcc.tcModuleNamesDict input - - Logger.LogBlockMessageStart filename LogCompilerFunctionId.IncrementalBuild_TypeCheck - let! (tcEnvAtEndOfFile, topAttribs, implFile, ccuSigForFile), tcState = - TypeCheckOneInputEventually - ((fun () -> hadParseErrors || errorLogger.ErrorCount > 0), - tcConfig, tcAcc.tcImports, - tcAcc.tcGlobals, - None, - TcResultsSink.WithSink sink, - tcAcc.tcState, input) - Logger.LogBlockMessageStop filename LogCompilerFunctionId.IncrementalBuild_TypeCheck - - /// Only keep the typed interface files when doing a "full" build for fsc.exe, otherwise just throw them away - let implFile = if keepAssemblyContents then implFile else None - let tcResolutions = if keepAllBackgroundResolutions then sink.GetResolutions() else TcResolutions.Empty - let tcEnvAtEndOfFile = (if keepAllBackgroundResolutions then tcEnvAtEndOfFile else tcState.TcEnvFromImpls) - let tcSymbolUses = if keepAllBackgroundSymbolUses then sink.GetSymbolUses() else TcSymbolUses.Empty - - // Build symbol keys - let itemKeyStore, semanticClassification = - if enableBackgroundItemKeyStoreAndSemanticClassification then - Logger.LogBlockMessageStart filename LogCompilerFunctionId.IncrementalBuild_CreateItemKeyStoreAndSemanticClassification - let sResolutions = sink.GetResolutions() - let builder = ItemKeyStoreBuilder() - let preventDuplicates = HashSet({ new IEqualityComparer with - member _.Equals((s1, e1): struct(pos * pos), (s2, e2): struct(pos * pos)) = Range.posEq s1 s2 && Range.posEq e1 e2 - member _.GetHashCode o = o.GetHashCode() }) - sResolutions.CapturedNameResolutions - |> Seq.iter (fun cnr -> - let r = cnr.Range - if preventDuplicates.Add struct(r.Start, r.End) then - builder.Write(cnr.Range, cnr.Item)) - - let res = builder.TryBuildAndReset(), sResolutions.GetSemanticClassification(tcAcc.tcGlobals, tcAcc.tcImports.GetImportMap(), sink.GetFormatSpecifierLocations(), None) - Logger.LogBlockMessageStop filename LogCompilerFunctionId.IncrementalBuild_CreateItemKeyStoreAndSemanticClassification - res - else - None, [||] - - RequireCompilationThread ctok // Note: events get raised on the CompilationThread - - fileChecked.Trigger filename - let newErrors = Array.append parseErrors (capturingErrorLogger.GetErrors()) - return {tcAcc with tcState=tcState - tcEnvAtEndOfFile=tcEnvAtEndOfFile - topAttribs=Some topAttribs - latestImplFile=implFile - latestCcuSigForFile=Some ccuSigForFile - tcResolutionsRev=tcResolutions :: tcAcc.tcResolutionsRev - tcSymbolUsesRev=tcSymbolUses :: tcAcc.tcSymbolUsesRev - tcOpenDeclarationsRev = sink.GetOpenDeclarations() :: tcAcc.tcOpenDeclarationsRev - tcErrorsRev = newErrors :: tcAcc.tcErrorsRev - tcModuleNamesDict = moduleNamesDict - tcDependencyFiles = filename :: tcAcc.tcDependencyFiles - itemKeyStore = itemKeyStore - semanticClassification = semanticClassification } - } - - // Run part of the Eventually<_> computation until a timeout is reached. If not complete, - // return a new Eventually<_> computation which recursively runs more of the computation. - // - When the whole thing is finished commit the error results sent through the errorLogger. - // - Each time we do real work we reinstall the CompilationGlobalsScope - let timeSlicedComputation = - fullComputation |> - Eventually.repeatedlyProgressUntilDoneOrTimeShareOverOrCanceled - maxTimeShareMilliseconds - CancellationToken.None - (fun ctok f -> - // Reinstall the compilation globals each time we start or restart - use unwind = new CompilationGlobalsScope (errorLogger, BuildPhase.TypeCheck) - f ctok) - - timeSlicedComputation - | _ -> - Eventually.Done tcAcc - + let TypeCheckTask ctok (prevSemanticModel: SemanticModel) syntaxTree: Eventually = + eventually { + RequireCompilationThread ctok + let! semanticModel = prevSemanticModel.Next(syntaxTree) + // Eagerly type check + // We need to do this to keep the expected behavior of events (namely fileChecked) when checking a file/project. + let! _ = semanticModel.GetState(enablePartialTypeChecking) + return semanticModel + } /// This is a build task function that gets placed into the build rules as the computation for a Vector.Demultiplex /// /// Finish up the typechecking to produce outputs for the rest of the compilation process - let FinalizeTypeCheckTask ctok (tcStates: TypeCheckAccumulator[]) = + let FinalizeTypeCheckTask ctok (semanticModels: SemanticModel[]) = cancellable { DoesNotRequireCompilerThreadTokenAndCouldPossiblyBeMadeConcurrent ctok @@ -1491,12 +1744,25 @@ type IncrementalBuilder(tcGlobals, frameworkTcImports, nonFrameworkAssemblyInput use _holder = new CompilationGlobalsScope(errorLogger, BuildPhase.TypeCheck) // Get the state at the end of the type-checking of the last file - let finalAcc = tcStates.[tcStates.Length-1] + let finalSemanticModel = semanticModels.[semanticModels.Length-1] + + let finalInfo = finalSemanticModel.TcInfo |> Eventually.force ctok // Finish the checking let (_tcEnvAtEndOfLastFile, topAttrs, mimpls, _), tcState = - let results = tcStates |> List.ofArray |> List.map (fun acc-> acc.tcEnvAtEndOfFile, defaultArg acc.topAttribs EmptyTopAttrs, acc.latestImplFile, acc.latestCcuSigForFile) - TypeCheckMultipleInputsFinish (results, finalAcc.tcState) + let results = + semanticModels + |> List.ofArray + |> List.map (fun semanticModel -> + let tcInfo, latestImplFile = + if enablePartialTypeChecking then + let tcInfo = semanticModel.TcInfo |> Eventually.force ctok + tcInfo, None + else + let tcInfo, tcInfoOptional = semanticModel.TcInfoWithOptional |> Eventually.force ctok + tcInfo, tcInfoOptional.latestImplFile + tcInfo.tcEnvAtEndOfFile, defaultArg tcInfo.topAttribs EmptyTopAttrs, latestImplFile, tcInfo.latestCcuSigForFile) + TypeCheckMultipleInputsFinish (results, finalInfo.tcState) let ilAssemRef, tcAssemblyDataOpt, tcAssemblyExprOpt = try @@ -1553,12 +1819,8 @@ type IncrementalBuilder(tcGlobals, frameworkTcImports, nonFrameworkAssemblyInput errorRecoveryNoRange e mkSimpleAssemblyRef assemblyName, None, None - let finalAccWithErrors = - { finalAcc with - tcErrorsRev = errorLogger.GetErrors() :: finalAcc.tcErrorsRev - topAttribs = Some topAttrs - } - return ilAssemRef, tcAssemblyDataOpt, tcAssemblyExprOpt, finalAccWithErrors + let finalSemanticModelWithErrors = finalSemanticModel.Finish((errorLogger.GetErrors() :: finalInfo.tcErrorsRev), Some topAttrs) |> Eventually.force ctok + return ilAssemRef, tcAssemblyDataOpt, tcAssemblyExprOpt, finalSemanticModelWithErrors } // END OF BUILD TASK FUNCTIONS @@ -1574,18 +1836,18 @@ type IncrementalBuilder(tcGlobals, frameworkTcImports, nonFrameworkAssemblyInput // Build let stampedFileNamesNode = Vector.Stamp "SourceFileTimeStamps" StampFileNameTask fileNamesNode let stampedReferencedAssembliesNode = Vector.Stamp "StampReferencedAssembly" StampReferencedAssemblyTask referencedAssembliesNode - let initialTcAccNode = Vector.Demultiplex "CombineImportedAssemblies" CombineImportedAssembliesTask stampedReferencedAssembliesNode - let tcStatesNode = Vector.ScanLeft "TypeCheckingStates" (fun ctok tcAcc n -> TypeCheckTask ctok tcAcc (ParseTask ctok n)) initialTcAccNode stampedFileNamesNode - let finalizedTypeCheckNode = Vector.Demultiplex "FinalizeTypeCheck" FinalizeTypeCheckTask tcStatesNode + let initialSemanticModelNode = Vector.Demultiplex "CombineImportedAssemblies" CombineImportedAssembliesTask stampedReferencedAssembliesNode + let semanticModelNodes = Vector.ScanLeft "TypeCheckingStates" (fun ctok semanticModel n -> TypeCheckTask ctok semanticModel (ParseTask ctok n)) initialSemanticModelNode stampedFileNamesNode + let finalizedSemanticModelNode = Vector.Demultiplex "FinalizeTypeCheck" FinalizeTypeCheckTask semanticModelNodes // Outputs let buildDescription = new BuildDescriptionScope () do buildDescription.DeclareVectorOutput stampedFileNamesNode do buildDescription.DeclareVectorOutput stampedReferencedAssembliesNode - do buildDescription.DeclareVectorOutput tcStatesNode - do buildDescription.DeclareScalarOutput initialTcAccNode - do buildDescription.DeclareScalarOutput finalizedTypeCheckNode + do buildDescription.DeclareVectorOutput semanticModelNodes + do buildDescription.DeclareScalarOutput initialSemanticModelNode + do buildDescription.DeclareScalarOutput finalizedSemanticModelNode // END OF BUILD DESCRIPTION // --------------------------------------------------------------------------------------------- @@ -1626,7 +1888,7 @@ type IncrementalBuilder(tcGlobals, frameworkTcImports, nonFrameworkAssemblyInput member __.Step (ctok: CompilationThreadToken) = cancellable { let cache = TimeStampCache defaultTimeStamp // One per step - let! res = IncrementalBuild.Step cache ctok SavePartialBuild (Target(tcStatesNode, None)) partialBuild + let! res = IncrementalBuild.Step cache ctok SavePartialBuild (Target(semanticModelNodes, None)) partialBuild match res with | None -> projectChecked.Trigger() @@ -1639,11 +1901,11 @@ type IncrementalBuilder(tcGlobals, frameworkTcImports, nonFrameworkAssemblyInput let slotOfFile = builder.GetSlotOfFileName filename let result = match slotOfFile with - | (*first file*) 0 -> GetScalarResult(initialTcAccNode, partialBuild) - | _ -> GetVectorResultBySlot(tcStatesNode, slotOfFile-1, partialBuild) + | (*first file*) 0 -> GetScalarResult(initialSemanticModelNode, partialBuild) + | _ -> GetVectorResultBySlot(semanticModelNodes, slotOfFile-1, partialBuild) match result with - | Some (tcAcc, timestamp) -> Some (PartialCheckResults.Create (tcAcc, timestamp)) + | Some (semanticModel, timestamp) -> Some (PartialCheckResults.Create (semanticModel, timestamp)) | _ -> None @@ -1651,8 +1913,8 @@ type IncrementalBuilder(tcGlobals, frameworkTcImports, nonFrameworkAssemblyInput let slotOfFile = builder.GetSlotOfFileName filename let cache = TimeStampCache defaultTimeStamp match slotOfFile with - | (*first file*) 0 -> IncrementalBuild.IsReady cache (Target(initialTcAccNode, None)) partialBuild - | _ -> IncrementalBuild.IsReady cache (Target(tcStatesNode, Some (slotOfFile-1))) partialBuild + | (*first file*) 0 -> IncrementalBuild.IsReady cache (Target(initialSemanticModelNode, None)) partialBuild + | _ -> IncrementalBuild.IsReady cache (Target(semanticModelNodes, Some (slotOfFile-1))) partialBuild member __.GetCheckResultsBeforeSlotInProject (ctok: CompilationThreadToken, slotOfFile) = cancellable { @@ -1661,15 +1923,15 @@ type IncrementalBuilder(tcGlobals, frameworkTcImports, nonFrameworkAssemblyInput cancellable { match slotOfFile with | (*first file*) 0 -> - let! build = IncrementalBuild.Eval cache ctok SavePartialBuild initialTcAccNode partialBuild - return GetScalarResult(initialTcAccNode, build) + let! build = IncrementalBuild.Eval cache ctok SavePartialBuild initialSemanticModelNode partialBuild + return GetScalarResult(initialSemanticModelNode, build) | _ -> - let! build = IncrementalBuild.EvalUpTo cache ctok SavePartialBuild (tcStatesNode, (slotOfFile-1)) partialBuild - return GetVectorResultBySlot(tcStatesNode, slotOfFile-1, build) + let! build = IncrementalBuild.EvalUpTo cache ctok SavePartialBuild (semanticModelNodes, (slotOfFile-1)) partialBuild + return GetVectorResultBySlot(semanticModelNodes, slotOfFile-1, build) } match result with - | Some (tcAcc, timestamp) -> return PartialCheckResults.Create (tcAcc, timestamp) + | Some (semanticModel, timestamp) -> return PartialCheckResults.Create (semanticModel, timestamp) | None -> return! failwith "Build was not evaluated, expected the results to be ready after 'Eval' (GetCheckResultsBeforeSlotInProject)." } @@ -1687,14 +1949,14 @@ type IncrementalBuilder(tcGlobals, frameworkTcImports, nonFrameworkAssemblyInput member __.GetCheckResultsAndImplementationsForProject(ctok: CompilationThreadToken) = cancellable { let cache = TimeStampCache defaultTimeStamp - let! build = IncrementalBuild.Eval cache ctok SavePartialBuild finalizedTypeCheckNode partialBuild - match GetScalarResult(finalizedTypeCheckNode, build) with - | Some ((ilAssemRef, tcAssemblyDataOpt, tcAssemblyExprOpt, tcAcc), timestamp) -> - return PartialCheckResults.Create (tcAcc, timestamp), ilAssemRef, tcAssemblyDataOpt, tcAssemblyExprOpt + let! build = IncrementalBuild.Eval cache ctok SavePartialBuild finalizedSemanticModelNode partialBuild + match GetScalarResult(finalizedSemanticModelNode, build) with + | Some ((ilAssemRef, tcAssemblyDataOpt, tcAssemblyExprOpt, semanticModel), timestamp) -> + return PartialCheckResults.Create (semanticModel, timestamp), ilAssemRef, tcAssemblyDataOpt, tcAssemblyExprOpt | None -> // helpers to diagnose https://github.com/Microsoft/visualfsharp/pull/2460/ - let brname = match GetTopLevelExprByName(build, finalizedTypeCheckNode.Name) with ScalarBuildRule se ->se.Id | _ -> Id 0xdeadbeef - let data = (finalizedTypeCheckNode.Name, + let brname = match GetTopLevelExprByName(build, finalizedSemanticModelNode.Name) with ScalarBuildRule se ->se.Id | _ -> Id 0xdeadbeef + let data = (finalizedSemanticModelNode.Name, ((build.Results :> IDictionary<_, _>).Keys |> Seq.toArray), brname, build.Results.ContainsKey brname, @@ -1748,7 +2010,8 @@ type IncrementalBuilder(tcGlobals, frameworkTcImports, nonFrameworkAssemblyInput | None -> return! failwith "Build was not evaluated, expected the results to be ready after 'Eval' (GetParseResultsForFile)." } // re-parse on demand instead of retaining - return ParseTask ctok results + let syntaxTree = ParseTask ctok results + return syntaxTree.Parse None } member __.SourceFiles = sourceFiles |> List.map (fun (_, f, _) -> f) @@ -1767,6 +2030,7 @@ type IncrementalBuilder(tcGlobals, frameworkTcImports, nonFrameworkAssemblyInput tryGetMetadataSnapshot, suggestNamesForErrors, keepAllBackgroundSymbolUses, enableBackgroundItemKeyStoreAndSemanticClassification, + enablePartialTypeChecking: bool, dependencyProviderOpt) = let useSimpleResolutionSwitch = "--simpleresolution" @@ -1892,6 +2156,7 @@ type IncrementalBuilder(tcGlobals, frameworkTcImports, nonFrameworkAssemblyInput maxTimeShareMilliseconds, keepAllBackgroundSymbolUses, enableBackgroundItemKeyStoreAndSemanticClassification, + enablePartialTypeChecking, dependencyProviderOpt) return Some builder with e -> diff --git a/src/fsharp/service/IncrementalBuild.fsi b/src/fsharp/service/IncrementalBuild.fsi index e7578ede1e7..db950e56473 100755 --- a/src/fsharp/service/IncrementalBuild.fsi +++ b/src/fsharp/service/IncrementalBuild.fsi @@ -42,61 +42,75 @@ module internal IncrementalBuilderEventTesting = val GetMostRecentIncrementalBuildEvents : int -> IBEvent list val GetCurrentIncrementalBuildEventNum : unit -> int -/// Represents the state in the incremental graph associated with checking a file -type internal PartialCheckResults = +/// Accumulated results of type checking. The minimum amount of state in order to continue type-checking following files. +[] +type internal TcInfo = { - /// This field is None if a major unrecovered error occurred when preparing the initial state - TcState : TcState - - TcImports: TcImports + tcState: TcState + tcEnvAtEndOfFile: TypeChecker.TcEnv - TcGlobals: TcGlobals + /// Disambiguation table for module names + moduleNamesDict: ModuleNamesDict - TcConfig: TcConfig + topAttribs: TypeChecker.TopAttribs option - /// This field is None if a major unrecovered error occurred when preparing the initial state - TcEnvAtEnd : TypeChecker.TcEnv + latestCcuSigForFile: ModuleOrNamespaceType option - /// Represents the collected errors from type checking - TcErrorsRev : (PhasedDiagnostic * FSharpErrorSeverity)[] list + /// Accumulated errors, last file first + tcErrorsRev:(PhasedDiagnostic * FSharpErrorSeverity)[] list - /// Represents the collected name resolutions from type checking - TcResolutionsRev: TcResolutions list + tcDependencyFiles: string list - /// Represents the collected uses of symbols from type checking - TcSymbolUsesRev: TcSymbolUses list + sigNameOpt: (string * SyntaxTree.QualifiedNameOfFile) option + } - /// Represents open declarations - TcOpenDeclarationsRev: OpenDeclaration[] list + member TcErrors: (PhasedDiagnostic * FSharpErrorSeverity)[] - /// Disambiguation table for module names - ModuleNamesDict: ModuleNamesDict +/// Accumulated results of type checking. Optional data that isn't needed to type-check a file, but needed for more information for in tooling. +[] +type internal TcInfoOptional = + { + /// Accumulated resolutions, last file first + tcResolutionsRev: TcResolutions list - TcDependencyFiles: string list + /// Accumulated symbol uses, last file first + tcSymbolUsesRev: TcSymbolUses list - /// Represents the collected attributes to apply to the module of assembly generates - TopAttribs: TypeChecker.TopAttribs option + /// Accumulated 'open' declarations, last file first + tcOpenDeclarationsRev: OpenDeclaration[] list - TimeStamp: DateTime - - /// Represents latest complete typechecked implementation file, including its typechecked signature if any. - /// Empty for a signature file. - LatestImplementationFile: TypedImplFile option - - /// Represents latest inferred signature contents. - LatestCcuSigForFile: ModuleOrNamespaceType option + /// Result of checking most recent file, if any + latestImplFile: TypedImplFile option /// If enabled, stores a linear list of ranges and strings that identify an Item(symbol) in a file. Used for background find all references. - ItemKeyStore: ItemKeyStore option + itemKeyStore: ItemKeyStore option /// If enabled, holds semantic classification information for Item(symbol)s in a file. - SemanticClassification: struct (range * SemanticClassificationType) [] + semanticClassification: struct (range * SemanticClassificationType) [] } - member TcErrors: (PhasedDiagnostic * FSharpErrorSeverity)[] - member TcSymbolUses: TcSymbolUses list +/// Represents the state in the incremental graph associated with checking a file +[] +type internal PartialCheckResults = + + member TcImports: TcImports + + member TcGlobals: TcGlobals + + member TcConfig: TcConfig + + member TimeStamp: DateTime + + member TcInfo: CompilationThreadToken -> TcInfo + + /// Can cause a second type-check if `enablePartialTypeChecking` is true in the checker. + /// Only use when it's absolutely necessary to get rich information on a file. + member TcInfoWithOptional: CompilationThreadToken -> TcInfo * TcInfoOptional + + member TimeStamp: DateTime + /// Manages an incremental build graph for the build of an F# project [] type internal IncrementalBuilder = @@ -204,6 +218,7 @@ type internal IncrementalBuilder = suggestNamesForErrors: bool * keepAllBackgroundSymbolUses: bool * enableBackgroundItemKeyStoreAndSemanticClassification: bool * + enablePartialTypeChecking: bool * dependencyProvider: DependencyProvider option -> Cancellable diff --git a/src/fsharp/service/service.fs b/src/fsharp/service/service.fs index d9b9056180e..6a239d6bf83 100644 --- a/src/fsharp/service/service.fs +++ b/src/fsharp/service/service.fs @@ -255,7 +255,7 @@ type ScriptClosureCacheToken() = interface LockToken // There is only one instance of this type, held in FSharpChecker -type BackgroundCompiler(legacyReferenceResolver, projectCacheSize, keepAssemblyContents, keepAllBackgroundResolutions, tryGetMetadataSnapshot, suggestNamesForErrors, keepAllBackgroundSymbolUses, enableBackgroundItemKeyStoreAndSemanticClassification) as self = +type BackgroundCompiler(legacyReferenceResolver, projectCacheSize, keepAssemblyContents, keepAllBackgroundResolutions, tryGetMetadataSnapshot, suggestNamesForErrors, keepAllBackgroundSymbolUses, enableBackgroundItemKeyStoreAndSemanticClassification, enablePartialTypeChecking) as self = // STATIC ROOT: FSharpLanguageServiceTestable.FSharpChecker.backgroundCompiler.reactor: The one and only Reactor let reactor = Reactor.Singleton let beforeFileChecked = Event() @@ -306,9 +306,8 @@ type BackgroundCompiler(legacyReferenceResolver, projectCacheSize, keepAssemblyC { new IProjectReference with member x.EvaluateRawContents(ctok) = cancellable { - Trace.TraceInformation("FCS: {0}.{1} ({2})", userOpName, "ParseAndCheckProjectImpl", nm) - let! r = self.ParseAndCheckProjectImpl(opts, ctok, userOpName + ".CheckReferencedProject("+nm+")") - return r.RawFSharpAssemblyData + Trace.TraceInformation("FCS: {0}.{1} ({2})", userOpName, "GetAssemblyData", nm) + return! self.GetAssemblyData(opts, ctok, userOpName + ".CheckReferencedProject("+nm+")") } member x.TryGetLogicalTimeStamp(cache, ctok) = self.TryGetLogicalTimeStampForProject(cache, ctok, opts, userOpName + ".TimeStampReferencedProject("+nm+")") @@ -323,6 +322,7 @@ type BackgroundCompiler(legacyReferenceResolver, projectCacheSize, keepAssemblyC options.UseScriptResolutionRules, keepAssemblyContents, keepAllBackgroundResolutions, FSharpCheckerResultsSettings.maxTimeShareMilliseconds, tryGetMetadataSnapshot, suggestNamesForErrors, keepAllBackgroundSymbolUses, enableBackgroundItemKeyStoreAndSemanticClassification, + enablePartialTypeChecking, (if options.UseScriptResolutionRules then Some dependencyProviderForScripts else None)) match builderOpt with @@ -510,7 +510,14 @@ type BackgroundCompiler(legacyReferenceResolver, projectCacheSize, keepAssemblyC textSnapshotInfo: obj option, fileVersion: int, builder: IncrementalBuilder, - tcPrior: PartialCheckResults, + tcConfig, + tcGlobals, + tcImports, + tcDependencyFiles, + timeStamp, + prevTcState, + prevModuleNamesDict, + prevTcErrors, creationErrors: FSharpErrorInfo[], userOpName: string) = @@ -533,29 +540,29 @@ type BackgroundCompiler(legacyReferenceResolver, projectCacheSize, keepAssemblyC let! checkAnswer = FSharpCheckFileResults.CheckOneFile (parseResults, - sourceText, - fileName, - options.ProjectFileName, - tcPrior.TcConfig, - tcPrior.TcGlobals, - tcPrior.TcImports, - tcPrior.TcState, - tcPrior.ModuleNamesDict, - loadClosure, - tcPrior.TcErrors, - reactorOps, - textSnapshotInfo, - userOpName, - options.IsIncompleteTypeCheckEnvironment, - builder, - Array.ofList tcPrior.TcDependencyFiles, - creationErrors, - parseResults.Errors, - keepAssemblyContents, - suggestNamesForErrors) - let parsingOptions = FSharpParsingOptions.FromTcConfig(tcPrior.TcConfig, Array.ofList builder.SourceFiles, options.UseScriptResolutionRules) - reactor.SetPreferredUILang tcPrior.TcConfig.preferredUiLang - bc.RecordTypeCheckFileInProjectResults(fileName, options, parsingOptions, parseResults, fileVersion, tcPrior.TimeStamp, Some checkAnswer, sourceText.GetHashCode()) + sourceText, + fileName, + options.ProjectFileName, + tcConfig, + tcGlobals, + tcImports, + prevTcState, + prevModuleNamesDict, + loadClosure, + prevTcErrors, + reactorOps, + textSnapshotInfo, + userOpName, + options.IsIncompleteTypeCheckEnvironment, + builder, + Array.ofList tcDependencyFiles, + creationErrors, + parseResults.Errors, + keepAssemblyContents, + suggestNamesForErrors) + let parsingOptions = FSharpParsingOptions.FromTcConfig(tcConfig, Array.ofList builder.SourceFiles, options.UseScriptResolutionRules) + reactor.SetPreferredUILang tcConfig.preferredUiLang + bc.RecordTypeCheckFileInProjectResults(fileName, options, parsingOptions, parseResults, fileVersion, timeStamp, Some checkAnswer, sourceText.GetHashCode()) return checkAnswer finally let dummy = ref () @@ -600,12 +607,17 @@ type BackgroundCompiler(legacyReferenceResolver, projectCacheSize, keepAssemblyC execWithReactorAsync <| fun ctok -> cancellable { DoesNotRequireCompilerThreadTokenAndCouldPossiblyBeMadeConcurrent ctok - return builder.GetCheckResultsBeforeFileInProjectEvenIfStale filename + let tcPrior = builder.GetCheckResultsBeforeFileInProjectEvenIfStale filename + return + tcPrior + |> Option.map (fun tcPrior -> + (tcPrior, tcPrior.TcInfo ctok) + ) } match tcPrior with - | Some tcPrior -> - let! checkResults = bc.CheckOneFileImpl(parseResults, sourceText, filename, options, textSnapshotInfo, fileVersion, builder, tcPrior, creationErrors, userOpName) + | Some(tcPrior, tcInfo) -> + let! checkResults = bc.CheckOneFileImpl(parseResults, sourceText, filename, options, textSnapshotInfo, fileVersion, builder, tcPrior.TcConfig, tcPrior.TcGlobals, tcPrior.TcImports, tcInfo.tcDependencyFiles, tcPrior.TimeStamp, tcInfo.tcState, tcInfo.moduleNamesDict, tcInfo.TcErrors, creationErrors, userOpName) return Some checkResults | None -> return None // the incremental builder was not up to date finally @@ -630,8 +642,13 @@ type BackgroundCompiler(legacyReferenceResolver, projectCacheSize, keepAssemblyC | Some (_, checkResults) -> return FSharpCheckFileAnswer.Succeeded checkResults | _ -> Trace.TraceInformation("FCS: {0}.{1} ({2})", userOpName, "CheckFileInProject.CacheMiss", filename) - let! tcPrior = execWithReactorAsync <| fun ctok -> builder.GetCheckResultsBeforeFileInProject (ctok, filename) - let! checkAnswer = bc.CheckOneFileImpl(parseResults, sourceText, filename, options, textSnapshotInfo, fileVersion, builder, tcPrior, creationErrors, userOpName) + let! tcPrior, tcInfo = + execWithReactorAsync <| fun ctok -> + cancellable { + let! tcPrior = builder.GetCheckResultsBeforeFileInProject (ctok, filename) + return (tcPrior, tcPrior.TcInfo ctok) + } + let! checkAnswer = bc.CheckOneFileImpl(parseResults, sourceText, filename, options, textSnapshotInfo, fileVersion, builder, tcPrior.TcConfig, tcPrior.TcGlobals, tcPrior.TcImports, tcInfo.tcDependencyFiles, tcPrior.TimeStamp, tcInfo.tcState, tcInfo.moduleNamesDict, tcInfo.TcErrors, creationErrors, userOpName) return checkAnswer finally bc.ImplicitlyStartCheckProjectInBackground(options, userOpName) @@ -668,14 +685,19 @@ type BackgroundCompiler(legacyReferenceResolver, projectCacheSize, keepAssemblyC | _ -> // todo this blocks the Reactor queue until all files up to the current are type checked. It's OK while editing the file, // but results with non cooperative blocking when a firts file from a project opened. - let! tcPrior = execWithReactorAsync <| fun ctok -> builder.GetCheckResultsBeforeFileInProject (ctok, filename) + let! tcPrior, tcInfo = + execWithReactorAsync <| fun ctok -> + cancellable { + let! tcPrior = builder.GetCheckResultsBeforeFileInProject (ctok, filename) + return (tcPrior, tcPrior.TcInfo ctok) + } // Do the parsing. let parsingOptions = FSharpParsingOptions.FromTcConfig(builder.TcConfig, Array.ofList (builder.SourceFiles), options.UseScriptResolutionRules) reactor.SetPreferredUILang tcPrior.TcConfig.preferredUiLang let parseErrors, parseTreeOpt, anyErrors = ParseAndCheckFile.parseFile (sourceText, filename, parsingOptions, userOpName, suggestNamesForErrors) let parseResults = FSharpParseFileResults(parseErrors, parseTreeOpt, anyErrors, builder.AllDependenciesDeprecated) - let! checkResults = bc.CheckOneFileImpl(parseResults, sourceText, filename, options, textSnapshotInfo, fileVersion, builder, tcPrior, creationErrors, userOpName) + let! checkResults = bc.CheckOneFileImpl(parseResults, sourceText, filename, options, textSnapshotInfo, fileVersion, builder, tcPrior.TcConfig, tcPrior.TcGlobals, tcPrior.TcImports, tcInfo.tcDependencyFiles, tcPrior.TimeStamp, tcInfo.tcState, tcInfo.moduleNamesDict, tcInfo.TcErrors, creationErrors, userOpName) Logger.LogBlockMessageStop (filename + strGuid + "-Successful") LogCompilerFunctionId.Service_ParseAndCheckFileInProject @@ -690,58 +712,71 @@ type BackgroundCompiler(legacyReferenceResolver, projectCacheSize, keepAssemblyC cancellable { let! builderOpt, creationErrors = getOrCreateBuilder (ctok, options, userOpName) match builderOpt with - | None -> + | None -> let parseResults = FSharpParseFileResults(creationErrors, None, true, [| |]) let typedResults = FSharpCheckFileResults.MakeEmpty(filename, creationErrors, reactorOps, keepAssemblyContents) return (parseResults, typedResults) | Some builder -> let! (parseTreeOpt, _, _, untypedErrors) = builder.GetParseResultsForFile (ctok, filename) let! tcProj = builder.GetCheckResultsAfterFileInProject (ctok, filename) + + let tcInfo, tcInfoOptional = tcProj.TcInfoWithOptional ctok + + let tcResolutionsRev = tcInfoOptional.tcResolutionsRev + let tcSymbolUsesRev = tcInfoOptional.tcSymbolUsesRev + let tcOpenDeclarationsRev = tcInfoOptional.tcOpenDeclarationsRev + let latestCcuSigForFile = tcInfo.latestCcuSigForFile + let tcState = tcInfo.tcState + let tcEnvAtEnd = tcInfo.tcEnvAtEndOfFile + let latestImplementationFile = tcInfoOptional.latestImplFile + let tcDependencyFiles = tcInfo.tcDependencyFiles + let tcErrors = tcInfo.TcErrors let errorOptions = builder.TcConfig.errorSeverityOptions let untypedErrors = [| yield! creationErrors; yield! ErrorHelpers.CreateErrorInfos (errorOptions, false, filename, untypedErrors, suggestNamesForErrors) |] - let tcErrors = [| yield! creationErrors; yield! ErrorHelpers.CreateErrorInfos (errorOptions, false, filename, tcProj.TcErrors, suggestNamesForErrors) |] + let tcErrors = [| yield! creationErrors; yield! ErrorHelpers.CreateErrorInfos (errorOptions, false, filename, tcErrors, suggestNamesForErrors) |] let parseResults = FSharpParseFileResults(errors = untypedErrors, input = parseTreeOpt, parseHadErrors = false, dependencyFiles = builder.AllDependenciesDeprecated) let loadClosure = scriptClosureCacheLock.AcquireLock (fun ltok -> scriptClosureCache.TryGet (ltok, options) ) let typedResults = FSharpCheckFileResults.Make (filename, - options.ProjectFileName, - tcProj.TcConfig, - tcProj.TcGlobals, - options.IsIncompleteTypeCheckEnvironment, - builder, - Array.ofList tcProj.TcDependencyFiles, - creationErrors, - parseResults.Errors, - tcErrors, - reactorOps, - keepAssemblyContents, - Option.get tcProj.LatestCcuSigForFile, - tcProj.TcState.Ccu, - tcProj.TcImports, - tcProj.TcEnvAtEnd.AccessRights, - List.head tcProj.TcResolutionsRev, - List.head tcProj.TcSymbolUsesRev, - tcProj.TcEnvAtEnd.NameEnv, - loadClosure, - tcProj.LatestImplementationFile, - List.head tcProj.TcOpenDeclarationsRev) + options.ProjectFileName, + tcProj.TcConfig, + tcProj.TcGlobals, + options.IsIncompleteTypeCheckEnvironment, + builder, + Array.ofList tcDependencyFiles, + creationErrors, + parseResults.Errors, + tcErrors, + reactorOps, + keepAssemblyContents, + Option.get latestCcuSigForFile, + tcState.Ccu, + tcProj.TcImports, + tcEnvAtEnd.AccessRights, + List.head tcResolutionsRev, + List.head tcSymbolUsesRev, + tcEnvAtEnd.NameEnv, + loadClosure, + latestImplementationFile, + List.head tcOpenDeclarationsRev) return (parseResults, typedResults) - }) + } + ) member __.FindReferencesInFile(filename: string, options: FSharpProjectOptions, symbol: FSharpSymbol, canInvalidateProject: bool, userOpName: string) = reactor.EnqueueAndAwaitOpAsync(userOpName, "FindReferencesInFile", filename, fun ctok -> - cancellable { + cancellable { let! builderOpt, _ = getOrCreateBuilderWithInvalidationFlag (ctok, options, canInvalidateProject, userOpName) match builderOpt with | None -> return Seq.empty | Some builder -> if builder.ContainsFile filename then let! checkResults = builder.GetCheckResultsAfterFileInProject (ctok, filename) - return - match checkResults.ItemKeyStore with - | None -> Seq.empty - | Some reader -> reader.FindAll symbol.Item + let _, tcInfoOptional = checkResults.TcInfoWithOptional ctok + match tcInfoOptional.itemKeyStore with + | None -> return Seq.empty + | Some reader -> return reader.FindAll symbol.Item else return Seq.empty }) @@ -749,13 +784,13 @@ type BackgroundCompiler(legacyReferenceResolver, projectCacheSize, keepAssemblyC member __.GetSemanticClassificationForFile(filename: string, options: FSharpProjectOptions, userOpName: string) = reactor.EnqueueAndAwaitOpAsync(userOpName, "GetSemanticClassificationForFile", filename, fun ctok -> cancellable { - let! builderOpt, _ = getOrCreateBuilder (ctok, options, userOpName) - match builderOpt with - | None -> return [||] - | Some builder -> - let! checkResults = builder.GetCheckResultsAfterFileInProject (ctok, filename) - return checkResults.SemanticClassification }) - + let! builderOpt, _ = getOrCreateBuilder (ctok, options, userOpName) + match builderOpt with + | None -> return [||] + | Some builder -> + let! checkResults = builder.GetCheckResultsAfterFileInProject (ctok, filename) + let _, tcInfoOptional = checkResults.TcInfoWithOptional ctok + return tcInfoOptional.semanticClassification }) /// Try to get recent approximate type check results for a file. member __.TryGetRecentCheckResultsForFile(filename: string, options:FSharpProjectOptions, sourceText: ISourceText option, _userOpName: string) = @@ -768,23 +803,43 @@ type BackgroundCompiler(legacyReferenceResolver, projectCacheSize, keepAssemblyC | None -> parseCacheLock.AcquireLock (fun ltok -> checkFileInProjectCachePossiblyStale.TryGet(ltok,(filename,options))) /// Parse and typecheck the whole project (the implementation, called recursively as project graph is evaluated) - member private __.ParseAndCheckProjectImpl(options, ctok, userOpName) : Cancellable = + member private __.ParseAndCheckProjectImpl(options, ctok, userOpName) = cancellable { - let! builderOpt,creationErrors = getOrCreateBuilder (ctok, options, userOpName) - match builderOpt with - | None -> - return FSharpCheckProjectResults (options.ProjectFileName, None, keepAssemblyContents, creationErrors, None) - | Some builder -> - let! (tcProj, ilAssemRef, tcAssemblyDataOpt, tcAssemblyExprOpt) = builder.GetCheckResultsAndImplementationsForProject(ctok) - let errorOptions = tcProj.TcConfig.errorSeverityOptions - let fileName = TcGlobals.DummyFileNameForRangesWithoutASpecificLocation - let errors = [| yield! creationErrors; yield! ErrorHelpers.CreateErrorInfos (errorOptions, true, fileName, tcProj.TcErrors, suggestNamesForErrors) |] - return FSharpCheckProjectResults (options.ProjectFileName, Some tcProj.TcConfig, keepAssemblyContents, errors, - Some(tcProj.TcGlobals, tcProj.TcImports, tcProj.TcState.Ccu, tcProj.TcState.CcuSig, - tcProj.TcSymbolUses, tcProj.TopAttribs, tcAssemblyDataOpt, ilAssemRef, - tcProj.TcEnvAtEnd.AccessRights, tcAssemblyExprOpt, Array.ofList tcProj.TcDependencyFiles)) + let! builderOpt,creationErrors = getOrCreateBuilder (ctok, options, userOpName) + match builderOpt with + | None -> + return FSharpCheckProjectResults (options.ProjectFileName, None, keepAssemblyContents, creationErrors, None) + | Some builder -> + let! (tcProj, ilAssemRef, tcAssemblyDataOpt, tcAssemblyExprOpt) = builder.GetCheckResultsAndImplementationsForProject(ctok) + let errorOptions = tcProj.TcConfig.errorSeverityOptions + let fileName = TcGlobals.DummyFileNameForRangesWithoutASpecificLocation + + let tcInfo, tcInfoOptional = tcProj.TcInfoWithOptional ctok + + let tcSymbolUses = tcInfoOptional.TcSymbolUses + let topAttribs = tcInfo.topAttribs + let tcState = tcInfo.tcState + let tcEnvAtEnd = tcInfo.tcEnvAtEndOfFile + let tcErrors = tcInfo.TcErrors + let tcDependencyFiles = tcInfo.tcDependencyFiles + let errors = [| yield! creationErrors; yield! ErrorHelpers.CreateErrorInfos (errorOptions, true, fileName, tcErrors, suggestNamesForErrors) |] + return FSharpCheckProjectResults (options.ProjectFileName, Some tcProj.TcConfig, keepAssemblyContents, errors, + Some(tcProj.TcGlobals, tcProj.TcImports, tcState.Ccu, tcState.CcuSig, + tcSymbolUses, topAttribs, tcAssemblyDataOpt, ilAssemRef, + tcEnvAtEnd.AccessRights, tcAssemblyExprOpt, Array.ofList tcDependencyFiles)) } + member _.GetAssemblyData(options, ctok, userOpName) = + cancellable { + let! builderOpt,_ = getOrCreateBuilder (ctok, options, userOpName) + match builderOpt with + | None -> + return None + | Some builder -> + let! (_, _, tcAssemblyDataOpt, _) = builder.GetCheckResultsAndImplementationsForProject(ctok) + return tcAssemblyDataOpt + } + /// Get the timestamp that would be on the output if fully built immediately member private __.TryGetLogicalTimeStampForProject(cache, ctok, options, userOpName: string) = @@ -795,6 +850,7 @@ type BackgroundCompiler(legacyReferenceResolver, projectCacheSize, keepAssemblyC | None -> None | Some builder -> Some (builder.GetLogicalTimeStampForProject(cache, ctok)) + /// Parse and typecheck the whole project. member bc.ParseAndCheckProject(options, userOpName) = reactor.EnqueueAndAwaitOpAsync(userOpName, "ParseAndCheckProject", options.ProjectFileName, fun ctok -> bc.ParseAndCheckProjectImpl(options, ctok, userOpName)) @@ -975,9 +1031,10 @@ type FSharpChecker(legacyReferenceResolver, tryGetMetadataSnapshot, suggestNamesForErrors, keepAllBackgroundSymbolUses, - enableBackgroundItemKeyStoreAndSemanticClassification) = + enableBackgroundItemKeyStoreAndSemanticClassification, + enablePartialTypeChecking) = - let backgroundCompiler = BackgroundCompiler(legacyReferenceResolver, projectCacheSize, keepAssemblyContents, keepAllBackgroundResolutions, tryGetMetadataSnapshot, suggestNamesForErrors, keepAllBackgroundSymbolUses, enableBackgroundItemKeyStoreAndSemanticClassification) + let backgroundCompiler = BackgroundCompiler(legacyReferenceResolver, projectCacheSize, keepAssemblyContents, keepAllBackgroundResolutions, tryGetMetadataSnapshot, suggestNamesForErrors, keepAllBackgroundSymbolUses, enableBackgroundItemKeyStoreAndSemanticClassification, enablePartialTypeChecking) static let globalInstance = lazy FSharpChecker.Create() @@ -994,7 +1051,7 @@ type FSharpChecker(legacyReferenceResolver, let maxMemEvent = new Event() /// Instantiate an interactive checker. - static member Create(?projectCacheSize, ?keepAssemblyContents, ?keepAllBackgroundResolutions, ?legacyReferenceResolver, ?tryGetMetadataSnapshot, ?suggestNamesForErrors, ?keepAllBackgroundSymbolUses, ?enableBackgroundItemKeyStoreAndSemanticClassification) = + static member Create(?projectCacheSize, ?keepAssemblyContents, ?keepAllBackgroundResolutions, ?legacyReferenceResolver, ?tryGetMetadataSnapshot, ?suggestNamesForErrors, ?keepAllBackgroundSymbolUses, ?enableBackgroundItemKeyStoreAndSemanticClassification, ?enablePartialTypeChecking) = let legacyReferenceResolver = match legacyReferenceResolver with @@ -1008,7 +1065,12 @@ type FSharpChecker(legacyReferenceResolver, let suggestNamesForErrors = defaultArg suggestNamesForErrors false let keepAllBackgroundSymbolUses = defaultArg keepAllBackgroundSymbolUses true let enableBackgroundItemKeyStoreAndSemanticClassification = defaultArg enableBackgroundItemKeyStoreAndSemanticClassification false - new FSharpChecker(legacyReferenceResolver, projectCacheSizeReal,keepAssemblyContents, keepAllBackgroundResolutions, tryGetMetadataSnapshot, suggestNamesForErrors, keepAllBackgroundSymbolUses, enableBackgroundItemKeyStoreAndSemanticClassification) + let enablePartialTypeChecking = defaultArg enablePartialTypeChecking false + + if keepAssemblyContents && enablePartialTypeChecking then + invalidArg "enablePartialTypeChecking" "'keepAssemblyContents' and 'enablePartialTypeChecking' cannot be both enabled." + + new FSharpChecker(legacyReferenceResolver, projectCacheSizeReal,keepAssemblyContents, keepAllBackgroundResolutions, tryGetMetadataSnapshot, suggestNamesForErrors, keepAllBackgroundSymbolUses, enableBackgroundItemKeyStoreAndSemanticClassification, enablePartialTypeChecking) member __.ReferenceResolver = legacyReferenceResolver diff --git a/src/fsharp/service/service.fsi b/src/fsharp/service/service.fsi index c0821183902..acb6b857ec4 100755 --- a/src/fsharp/service/service.fsi +++ b/src/fsharp/service/service.fsi @@ -78,10 +78,11 @@ type public FSharpChecker = /// Indicate whether name suggestion should be enabled /// Indicate whether all symbol uses should be kept in background checking /// Indicates whether a table of symbol keys should be kept for background compilation + /// Indicates whether to perform partial type checking. Cannot be set to true if keepAssmeblyContents is true. If set to true, can cause duplicate type-checks when richer information on a file is needed, but can skip background type-checking entirely on implementation files with signature files. static member Create: ?projectCacheSize: int * ?keepAssemblyContents: bool * ?keepAllBackgroundResolutions: bool * ?legacyReferenceResolver: ReferenceResolver.Resolver * ?tryGetMetadataSnapshot: ILReaderTryGetMetadataSnapshot * - ?suggestNamesForErrors: bool * ?keepAllBackgroundSymbolUses: bool * ?enableBackgroundItemKeyStoreAndSemanticClassification: bool + ?suggestNamesForErrors: bool * ?keepAllBackgroundSymbolUses: bool * ?enableBackgroundItemKeyStoreAndSemanticClassification: bool * ?enablePartialTypeChecking: bool -> FSharpChecker /// @@ -215,6 +216,7 @@ type public FSharpChecker = /// /// Parse and typecheck all files in a project. /// All files are read from the FileSystem API + /// Can cause a second type-check on the entire project when `enablePartialTypeChecking` is true on the FSharpChecker. /// /// /// The options for the project or script. @@ -294,6 +296,7 @@ type public FSharpChecker = /// /// Like CheckFileInProject, but uses the existing results from the background builder. /// All files are read from the FileSystem API, including the file being checked. + /// Can cause a second type-check when `enablePartialTypeChecking` is true on the FSharpChecker. /// /// /// The filename for the file. @@ -304,6 +307,7 @@ type public FSharpChecker = /// /// Optimized find references for a given symbol in a file of project. /// All files are read from the FileSystem API, including the file being checked. + /// Can cause a second type-check when `enablePartialTypeChecking` is true on the FSharpChecker. /// /// /// The filename for the file. @@ -316,6 +320,7 @@ type public FSharpChecker = /// /// Get semantic classification for a file. /// All files are read from the FileSystem API, including the file being checked. + /// Can cause a second type-check when `enablePartialTypeChecking` is true on the FSharpChecker. /// /// /// The filename for the file. diff --git a/tests/FSharp.Compiler.Service.Tests/SurfaceArea.netstandard.fs b/tests/FSharp.Compiler.Service.Tests/SurfaceArea.netstandard.fs index a200c265924..b65ead5d9fa 100644 --- a/tests/FSharp.Compiler.Service.Tests/SurfaceArea.netstandard.fs +++ b/tests/FSharp.Compiler.Service.Tests/SurfaceArea.netstandard.fs @@ -21481,7 +21481,7 @@ FSharp.Compiler.SourceCodeServices.FSharpCheckProjectResults: System.String[] De FSharp.Compiler.SourceCodeServices.FSharpCheckProjectResults: System.String[] get_DependencyFiles() FSharp.Compiler.SourceCodeServices.FSharpChecker: Boolean ImplicitlyStartBackgroundWork FSharp.Compiler.SourceCodeServices.FSharpChecker: Boolean get_ImplicitlyStartBackgroundWork() -FSharp.Compiler.SourceCodeServices.FSharpChecker: FSharp.Compiler.SourceCodeServices.FSharpChecker Create(Microsoft.FSharp.Core.FSharpOption`1[System.Int32], Microsoft.FSharp.Core.FSharpOption`1[System.Boolean], Microsoft.FSharp.Core.FSharpOption`1[System.Boolean], Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.ReferenceResolver+Resolver], Microsoft.FSharp.Core.FSharpOption`1[Microsoft.FSharp.Core.FSharpFunc`2[System.Tuple`2[System.String,System.DateTime],Microsoft.FSharp.Core.FSharpOption`1[System.Tuple`3[System.Object,System.IntPtr,System.Int32]]]], Microsoft.FSharp.Core.FSharpOption`1[System.Boolean], Microsoft.FSharp.Core.FSharpOption`1[System.Boolean], Microsoft.FSharp.Core.FSharpOption`1[System.Boolean]) +FSharp.Compiler.SourceCodeServices.FSharpChecker: FSharp.Compiler.SourceCodeServices.FSharpChecker Create(Microsoft.FSharp.Core.FSharpOption`1[System.Int32], Microsoft.FSharp.Core.FSharpOption`1[System.Boolean], Microsoft.FSharp.Core.FSharpOption`1[System.Boolean], Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.ReferenceResolver+Resolver], Microsoft.FSharp.Core.FSharpOption`1[Microsoft.FSharp.Core.FSharpFunc`2[System.Tuple`2[System.String,System.DateTime],Microsoft.FSharp.Core.FSharpOption`1[System.Tuple`3[System.Object,System.IntPtr,System.Int32]]]], 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.Boolean]) FSharp.Compiler.SourceCodeServices.FSharpChecker: FSharp.Compiler.SourceCodeServices.FSharpChecker Instance FSharp.Compiler.SourceCodeServices.FSharpChecker: FSharp.Compiler.SourceCodeServices.FSharpChecker get_Instance() FSharp.Compiler.SourceCodeServices.FSharpChecker: FSharp.Compiler.SourceCodeServices.FSharpProjectOptions GetProjectOptionsFromCommandLineArgs(System.String, System.String[], Microsoft.FSharp.Core.FSharpOption`1[System.DateTime], Microsoft.FSharp.Core.FSharpOption`1[System.Object]) diff --git a/vsintegration/src/FSharp.Editor/LanguageService/FSharpCheckerProvider.fs b/vsintegration/src/FSharp.Editor/LanguageService/FSharpCheckerProvider.fs index 176e0f8ccbd..05d7f9b1309 100644 --- a/vsintegration/src/FSharp.Editor/LanguageService/FSharpCheckerProvider.fs +++ b/vsintegration/src/FSharp.Editor/LanguageService/FSharpCheckerProvider.fs @@ -60,7 +60,8 @@ type internal FSharpCheckerProvider legacyReferenceResolver=LegacyMSBuildReferenceResolver.getResolver(), tryGetMetadataSnapshot = tryGetMetadataSnapshot, keepAllBackgroundSymbolUses = false, - enableBackgroundItemKeyStoreAndSemanticClassification = true) + enableBackgroundItemKeyStoreAndSemanticClassification = true, + enablePartialTypeChecking = true) // This is one half of the bridge between the F# background builder and the Roslyn analysis engine. // When the F# background builder refreshes the background semantic build context for a file,