diff --git a/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md b/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md index 3c823881ec1..ae86b1522a0 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md +++ b/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md @@ -178,6 +178,7 @@ * IL: cache C# extension methods per CCU ([PR #20256](https://github.com/dotnet/fsharp/pull/20256)) * Nullness warning FS3261 on dotted method or property access (e.g. `x.Member`) now underlines the receiver expression and includes the member name and (when known) the binding name in the message. ([Issue #19658](https://github.com/dotnet/fsharp/issues/19658), [PR #19814](https://github.com/dotnet/fsharp/pull/19814)) +* Import: share assembly CCUs between projects ([PR #20296](https://github.com/dotnet/fsharp/pull/20296)) * IL: share the pickled references ([PR #20301](https://github.com/dotnet/fsharp/pull/20301)) * Direct delegate construction ([PR ##19993](https://github.com/dotnet/fsharp/pull/19993)) * IL: add `ILPreNamespace`, make `ILPreTypeDef` creation lazy ([PR #20092](https://github.com/dotnet/fsharp/pull/20092)) diff --git a/src/Compiler/Driver/CompilerConfig.fs b/src/Compiler/Driver/CompilerConfig.fs index c1da8d3c1fd..00d1a9acb2f 100644 --- a/src/Compiler/Driver/CompilerConfig.fs +++ b/src/Compiler/Driver/CompilerConfig.fs @@ -445,6 +445,13 @@ type TypeCheckingConfig = DumpGraph: bool } +[] +type ImportReuseKey = + { + LangVersion: decimal + CheckNullness: bool + } + [] type TcConfigBuilder = { @@ -645,6 +652,8 @@ type TcConfigBuilder = mutable parallelReferenceResolution: ParallelReferenceResolution + mutable shareImportedAssemblies: bool + mutable captureIdentifiersWhenParsing: bool mutable typeCheckingConfig: TypeCheckingConfig @@ -844,6 +853,7 @@ type TcConfigBuilder = xmlDocInfoLoader = None exiter = QuitProcessExiter parallelReferenceResolution = ParallelReferenceResolution.On + shareImportedAssemblies = true captureIdentifiersWhenParsing = false typeCheckingConfig = { @@ -1397,8 +1407,16 @@ type TcConfig private (data: TcConfigBuilder, validate: bool) = member _.xmlDocInfoLoader = data.xmlDocInfoLoader member _.exiter = data.exiter member _.parallelReferenceResolution = data.parallelReferenceResolution + member _.shareImportedAssemblies = data.shareImportedAssemblies member _.captureIdentifiersWhenParsing = data.captureIdentifiersWhenParsing member _.typeCheckingConfig = data.typeCheckingConfig + + member _.importReuseKey = + { + ImportReuseKey.LangVersion = data.langVersion.SpecifiedVersion + ImportReuseKey.CheckNullness = data.checkNullness + } + member _.dumpSignatureData = data.dumpSignatureData member _.realsig = data.realsig member _.compilationMode = data.compilationMode diff --git a/src/Compiler/Driver/CompilerConfig.fsi b/src/Compiler/Driver/CompilerConfig.fsi index 89731f6decc..5ed1050561c 100644 --- a/src/Compiler/Driver/CompilerConfig.fsi +++ b/src/Compiler/Driver/CompilerConfig.fsi @@ -225,6 +225,12 @@ type TypeCheckingConfig = DumpGraph: bool } +/// A field belongs here when two projects differing in it cannot reuse one imported form +[] +type ImportReuseKey = + { LangVersion: decimal + CheckNullness: bool } + [] type TcConfigBuilder = { @@ -515,6 +521,8 @@ type TcConfigBuilder = mutable parallelReferenceResolution: ParallelReferenceResolution + mutable shareImportedAssemblies: bool + mutable captureIdentifiersWhenParsing: bool mutable typeCheckingConfig: TypeCheckingConfig @@ -888,10 +896,14 @@ type TcConfig = member parallelReferenceResolution: ParallelReferenceResolution + member shareImportedAssemblies: bool + member captureIdentifiersWhenParsing: bool member typeCheckingConfig: TypeCheckingConfig + member importReuseKey: ImportReuseKey + member dumpSignatureData: bool member realsig: bool diff --git a/src/Compiler/Driver/CompilerImports.fs b/src/Compiler/Driver/CompilerImports.fs index e9e5c2d4206..e311c56c110 100644 --- a/src/Compiler/Driver/CompilerImports.fs +++ b/src/Compiler/Driver/CompilerImports.fs @@ -15,6 +15,7 @@ open System.Reflection open Internal.Utilities open Internal.Utilities.Collections open Internal.Utilities.FSharpEnvironment +open Internal.Utilities.Hashing open Internal.Utilities.Library open Internal.Utilities.Library.Extras @@ -430,6 +431,176 @@ type AssemblyResolution = this.ilAssemblyRef <- Some assemblyRef assemblyRef +module internal SharedImportedCcus = + + type SimpleAssemblyName = string + + /// ILAssemblyRef cannot serve: it does not separate one package's builds for different frameworks + [] + type AssemblyFileId = + | AssemblyFileId of text: string + + member this.Text = + match this with + | AssemblyFileId text -> text + + type AssemblyKeyInfo = + { + File: AssemblyFileId + + /// Its references and its type-forwarder targets, which is all a facade can reach + References: SimpleAssemblyName list + } + + [] + type SharedCcuKey = + { + /// Hash over the file each assembly of the closure resolved to + Closure: string + + /// The layer the entry resolves through, and so the TcGlobals import reads + FrameworkStamp: int64 + + /// Set where an assembly carries several F# ccus + CcuName: SimpleAssemblyName option + } + + /// Weak: an entry is held by the projects using it, and holds the rest of its own closure + let private cache = ConcurrentDictionary>() + + let private nameComparer = StringComparer.OrdinalIgnoreCase + + /// What a shared ccu may close over besides its own closure. Functions because TcImports comes later. + type FrameworkLayer = + { + Globals: unit -> TcGlobals + Resolve: CompilationThreadToken * range * ILAssemblyRef -> CcuResolutionResult + XmlDoc: string -> XmlDocumentationInfo option + } + + /// What a shared ccu resolves through instead of the TcImports that first imported it: its own + /// closure, then the framework layer. Holding the closure strongly is what lets the cache be weak. + type SharedImportContext(layer: FrameworkLayer, closure: SimpleAssemblyName list) = + + let refs = ConcurrentDictionary(nameComparer) + + let pending = ResizeArray() + + let loader = + { new AssemblyLoader with + member _.FindCcuFromAssemblyRef(ctok, m, ilAssemblyRef: ILAssemblyRef) = + match refs.TryGetValue ilAssemblyRef.Name with + | true, ccu -> ResolvedCcu ccu + | _ -> layer.Resolve(ctok, m, ilAssemblyRef) + + member _.TryFindXmlDocumentationInfo assemblyName = layer.XmlDoc assemblyName + +#if !NO_TYPEPROVIDERS + member _.GetProvidedAssemblyInfo(_ctok, m, _assembly) = + error (InternalError("a shared ccu cannot import provided types", m)) + + member _.RecordGeneratedTypeRoot _root = + error (InternalError("a shared ccu cannot record a generated type root", range0)) +#endif + } + + let importMap = lazy ImportMap(layer.Globals(), loader) + + member _.Closure = closure + + member _.AddClosureRef(name: SimpleAssemblyName, ccu: CcuThunk) = refs[name] <- ccu + + member _.GetImportMap() = importMap.Force() + + /// Publishing before the closure is known would hand another project an entry resolving nothing + member _.HoldForPublication(key: SharedCcuKey, ccu: CcuThunk) = pending.Add(key, ccu) + + member _.Pending = pending + + type SharedImport = + { + Key: SharedCcuKey + Context: SharedImportContext + } + + type ShareableAssembly = + { + Key: SharedCcuKey + Closure: SimpleAssemblyName list + } + + /// Names walked to inside the batch, then those leaving it - framework assemblies the stamp pins + let private closureOf (assemblies: Dictionary) root = + let inside = HashSet(nameComparer) + let outside = HashSet(nameComparer) + + let rec walk n = + if inside.Add n then + for r in assemblies[n].References do + if assemblies.ContainsKey r then + walk r + else + outside.Add r |> ignore + + walk root + inside, outside + + /// No key where the closure is not shareable: entities and their per-CCU caches would point into one + /// project's copy of an unshared assembly. + let computeKeys (frameworkStamp: int64) (assemblies: Dictionary) isShareable = + let keys = Dictionary(nameComparer) + + for KeyValue(name, assembly) in assemblies do + let inside, outside = closureOf assemblies name + + if Seq.forall isShareable inside then + let parts = + Seq.append + (inside |> Seq.map (fun n -> n + "|" + assemblies[n].File.Text)) + (outside |> Seq.map (fun r -> "unresolved:" + r)) + |> Seq.sort + + keys[name] <- + { + Key = + { + Closure = Md5StringHasher.addStrings parts Md5StringHasher.empty + FrameworkStamp = frameworkStamp + CcuName = None + } + Closure = assembly.References + } + + keys + + let private tryGet (key: SharedCcuKey) = + match cache.TryGetValue key with + | true, wr -> + match wr.TryGetTarget() with + | true, ccu -> Some ccu + | _ -> + cache.TryRemove key |> ignore + None + | _ -> None + + /// `build` is not called on a hit, which is what lets the F# path skip unpickling entirely + let getOrBuild (entry: SharedImport option) (build: unit -> CcuThunk) = + match entry with + | None -> build () + | Some entry -> + match tryGet entry.Key with + | Some ccu -> ccu + | None -> + let ccu = build () + entry.Context.HoldForPublication(entry.Key, ccu) + ccu + + /// Last writer wins: two projects importing at once each keep the ccu they built, both consistent + let add (key: SharedCcuKey) (ccu: CcuThunk) = + cache[key] <- WeakReference ccu + + let clear () = cache.Clear() + type ImportedBinary = { FileName: string @@ -1223,6 +1394,10 @@ and [] TcImports let tciLock = TcImportsLock() + /// For cache keys: an identity hash is neither unique nor stable, and aliasing two layers would + /// silently mix their ccus + let stamp = newStamp () + //---- Start protected by tciLock ------- let mutable resolutions = initialResolutions let mutable dllInfos: ImportedBinary list = [] @@ -1364,6 +1539,15 @@ and [] TcImports | Some importsBase -> importsBase.AllAssemblyResolutions() @ ars | None -> ars) + member _.Stamp = stamp + + member _.GetImportReuseKey ctok = (tcConfigP.Get ctok).importReuseKey + + member tcImports.KeyPinnedLayer = + match importsBase with + | Some b -> b + | None -> tcImports + member tcImports.TryFindDllInfo(ctok: CompilationThreadToken, m, assemblyName, lookupOnly) = CheckDisposed() @@ -2059,28 +2243,50 @@ and [] TcImports // Compact Framework binaries must use this. However it is not // clear when else it is required, e.g. for Mono. - member tcImports.PrepareToImportReferencedILAssembly(ctok, m, fileName, dllinfo: ImportedBinary) = + member tcImports.PrepareToImportReferencedILAssembly + (ctok, m, fileName, dllinfo: ImportedBinary, ?shared: SharedImportedCcus.SharedImport) + = CheckDisposed() let tcConfig = tcConfigP.Get ctok assert dllinfo.RawMetadata.TryGetILModuleDef().IsSome let ilModule = dllinfo.RawMetadata.TryGetILModuleDef().Value let ilScopeRef = dllinfo.ILScopeRef - let auxModuleLoader = tcImports.MkLoaderForMultiModuleILAssemblies ctok m let invalidateCcu = Event<_>() - let ccu = + let sharedContext = shared |> Option.map (fun s -> s.Context) + + // Everything the ccu later resolves goes through this, so it decides what the ccu is bound to + let amap = + match sharedContext with + | Some ctx -> ctx.GetImportMap + | None -> tcImports.GetImportMap + + let auxModuleLoader = + match sharedContext with + | Some _ -> fun scoref -> error (InternalError(sprintf "a shared ccu cannot load the auxiliary module %A" scoref, m)) + | None -> tcImports.MkLoaderForMultiModuleILAssemblies ctok m + + // Meaningless in a shared ccu: SymbolHelpers.fileNameOfItem joins it with a path from metadata + let sourceDir = + match shared with + | Some _ -> "" + | None -> tcConfig.implicitIncludeDir + + let importFresh () = ImportILAssembly( - tcImports.GetImportMap, + amap, m, auxModuleLoader, tcConfig.xmlDocInfoLoader, ilScopeRef, - tcConfig.implicitIncludeDir, + sourceDir, Some fileName, ilModule, invalidateCcu.Publish ) + let ccu = SharedImportedCcus.getOrBuild shared importFresh + let ccuinfo = { FSharpViewOfMetadata = ccu @@ -2107,8 +2313,20 @@ and [] TcImports phase2 - member tcImports.PrepareToImportReferencedFSharpAssembly(ctok, m, fileName, dllinfo: ImportedBinary) = + member tcImports.PrepareToImportReferencedFSharpAssembly + (ctok, m, fileName, dllinfo: ImportedBinary, ?shared: SharedImportedCcus.SharedImport) + = CheckDisposed() + + let sharedContext = shared |> Option.map (fun s -> s.Context) + + // GetTcGlobals reaches the same object, but through a TcImports a cached ccu must not hold + let globalsOwner = tcImports.KeyPinnedLayer + + let amap = + match sharedContext with + | Some ctx -> ctx.GetImportMap + | None -> tcImports.GetImportMap #if !NO_TYPEPROVIDERS let tcConfig = tcConfigP.Get ctok #endif @@ -2122,54 +2340,69 @@ and [] TcImports let ccuRawDataAndInfos = ilModule.GetRawFSharpSignatureData(m, ilShortAssemName, fileName) |> List.map (fun (ccuName, (sigDataReader, sigDataReaderB)) -> - let data = - GetSignatureData(fileName, ilScopeRef, ilModule.TryGetILModuleDef(), sigDataReader, sigDataReaderB) + let entry = + shared + |> Option.map (fun s -> + { s with + Key = { s.Key with CcuName = Some ccuName } + }) let optDatas = Map.ofList optDataReaders - let minfo: PickledCcuInfo = data.RawData - let mspec = minfo.mspec - - if mspec.DisplayName = "FSharp.Core" then - updateSeqTypeIsPrefix mspec - #if !NO_TYPEPROVIDERS let invalidateCcu = Event<_>() #endif - let codeDir = minfo.compileTimeWorkingDir + // None on a hit: nothing is left to relink, and the signature data is never read + let mutable dataOpt = None - // note: for some fields we fix up this information later - let ccuData: CcuData = - { - ILScopeRef = ilScopeRef - Stamp = newStamp () - FileName = Some fileName - QualifiedName = Some(ilScopeRef.QualifiedName) - SourceCodeDirectory = codeDir - IsFSharp = true - Contents = mspec + let importFresh () = + let data = + GetSignatureData(fileName, ilScopeRef, ilModule.TryGetILModuleDef(), sigDataReader, sigDataReaderB) + + let minfo: PickledCcuInfo = data.RawData + let mspec = minfo.mspec + + // Fixes up the unpickled contents, so a hit gets it too + if mspec.DisplayName = "FSharp.Core" then + updateSeqTypeIsPrefix mspec + + let codeDir = minfo.compileTimeWorkingDir + + // note: for some fields we fix up this information later + let ccuData: CcuData = + { + ILScopeRef = ilScopeRef + Stamp = newStamp () + FileName = Some fileName + QualifiedName = Some(ilScopeRef.QualifiedName) + SourceCodeDirectory = codeDir + IsFSharp = true + Contents = mspec #if !NO_TYPEPROVIDERS - InvalidateEvent = invalidateCcu.Publish - IsProviderGenerated = false - ImportProvidedType = (fun ty -> ImportProvidedType (tcImports.GetImportMap()) m ty) + InvalidateEvent = invalidateCcu.Publish + IsProviderGenerated = false + ImportProvidedType = (fun ty -> ImportProvidedType (amap ()) m ty) #endif - TryGetILModuleDef = ilModule.TryGetILModuleDef - UsesFSharp20PlusQuotations = minfo.usesQuotations - MemberSignatureEquality = (fun ty1 ty2 -> typeEquivAux EraseAll (tcImports.GetTcGlobals()) ty1 ty2) - TypeForwarders = ImportILAssemblyTypeForwarders(tcImports.GetImportMap, m, ilModule.GetRawTypeForwarders()) - CSharpStyleExtensionMembersCache = ConcurrentDictionary(1, 0) + TryGetILModuleDef = ilModule.TryGetILModuleDef + UsesFSharp20PlusQuotations = minfo.usesQuotations + MemberSignatureEquality = (fun ty1 ty2 -> typeEquivAux EraseAll (globalsOwner.GetTcGlobals()) ty1 ty2) + TypeForwarders = ImportILAssemblyTypeForwarders(amap, m, ilModule.GetRawTypeForwarders()) + CSharpStyleExtensionMembersCache = ConcurrentDictionary(1, 0) #if !NO_TYPEPROVIDERS - XmlDocumentationInfo = - match tcConfig.xmlDocInfoLoader with - | Some xmlDocInfoLoader -> xmlDocInfoLoader.TryLoad(fileName) - | _ -> None + XmlDocumentationInfo = + match tcConfig.xmlDocInfoLoader with + | Some xmlDocInfoLoader -> xmlDocInfoLoader.TryLoad(fileName) + | _ -> None #else - XmlDocumentationInfo = None + XmlDocumentationInfo = None #endif - } + } - let ccu = CcuThunk.Create(ccuName, ccuData) + dataOpt <- Some data + CcuThunk.Create(ccuName, ccuData) + + let ccu = SharedImportedCcus.getOrBuild entry importFresh let optdata = InterruptibleLazy(fun _ -> @@ -2228,7 +2461,7 @@ and [] TcImports #else () #endif - data, ccuinfo, phase2) + dataOpt, ccuinfo, phase2) // Register all before relinking to cope with mutually-referential ccus ccuRawDataAndInfos |> List.iter (p23 >> tcImports.RegisterCcu) @@ -2236,18 +2469,22 @@ and [] TcImports let phase2 () = // Relink ccuRawDataAndInfos - |> List.iter (fun (data, _, _) -> - let fixupThunk () = - data.OptionalFixup(fun nm -> availableToOptionalCcu (tcImports.FindCcu(ctok, m, nm, lookupOnly = false))) - |> ignore + |> List.iter (fun (dataOpt, _, _) -> + match dataOpt with + | None -> () + | Some data -> + + let fixupThunk () = + data.OptionalFixup(fun nm -> availableToOptionalCcu (tcImports.FindCcu(ctok, m, nm, lookupOnly = false))) + |> ignore - fixupThunk () + fixupThunk () - for ccuThunk in data.FixupThunks do - if ccuThunk.IsUnresolvedReference then - tciLock.AcquireLock(fun tcitok -> - RequireTcImportsLock(tcitok, ccuThunks) - ccuThunks.Add(ccuThunk, fixupThunk))) + for ccuThunk in data.FixupThunks do + if ccuThunk.IsUnresolvedReference then + tciLock.AcquireLock(fun tcitok -> + RequireTcImportsLock(tcitok, ccuThunks) + ccuThunks.Add(ccuThunk, fixupThunk))) #if !NO_TYPEPROVIDERS ccuRawDataAndInfos |> List.iter (fun (_, _, phase2) -> phase2 ()) #endif @@ -2258,6 +2495,15 @@ and [] TcImports // NOTE: When used in the Language Service this can cause the transitive checking of projects. Hence it must be cancellable. member tcImports.RegisterAndImportReferencedAssemblies(ctok, nms: AssemblyResolution list) = + let frameworkLayer: SharedImportedCcus.FrameworkLayer = + let layer = tcImports.KeyPinnedLayer + + { + Globals = layer.GetTcGlobals + Resolve = fun (ctok, m, aref) -> layer.FindCcuFromAssemblyRef(ctok, m, aref) + XmlDoc = layer.TryFindXmlDocumentationInfo + } + let tryGetAssemblyData (r: AssemblyResolution) = async { CheckDisposed() @@ -2290,12 +2536,143 @@ and [] TcImports return None } - let registerDll (r: AssemblyResolution, assemblyData: IRawFSharpAssemblyData) = + /// Also reports a multi-module assembly, whose auxiliary modules need the importing project + let reachableAssemblyNames self (data: IRawFSharpAssemblyData) = + let names = ResizeArray() + names.Add self + + for aref in data.ILAssemblyRefs do + names.Add aref.Name + + let mutable multiModule = false + + match data.TryGetILModuleDef() |> Option.bind (fun ilModule -> ilModule.Manifest) with + | Some manifest -> + for e in manifest.ExportedTypes.AsList() do + match e.ScopeRef with + | ILScopeRef.Assembly aref -> names.Add aref.Name + | ILScopeRef.Module _ -> multiModule <- true + | _ -> () + | None -> () + + List.distinct (List.ofSeq names), multiModule + +#if NO_TYPEPROVIDERS + let isTypeProviderAssembly (_: IRawFSharpAssemblyData) = false +#else + let isTypeProviderAssembly (data: IRawFSharpAssemblyData) = + match data.TryGetILModuleDef() with + | Some ilModule -> + ilModule.ManifestOfAssembly.CustomAttrs.AsList() + |> List.exists (TryDecodeTypeProviderAssemblyAttr >> Option.isSome) + | None -> false +#endif + + let fileIdentity (r: AssemblyResolution) : SharedImportedCcus.AssemblyFileId = + let writeStamp = + try + string (FileSystem.GetLastWriteTimeShim r.resolvedPath).Ticks + with _ -> + "nostamp" + + SharedImportedCcus.AssemblyFileId(r.resolvedPath + "|" + writeStamp) + + let sharedKeys (all: (AssemblyResolution * IRawFSharpAssemblyData) list) = + let ic = StringComparer.OrdinalIgnoreCase + let short (p: string) = Path.GetFileNameWithoutExtension p + + let frameworkStamp = + match importsBase with + | Some b -> b.Stamp + | None -> 0L + + // Two resolutions claiming one name would key whichever was seen last, so neither is shared + let ambiguous = + all + |> List.countBy (fun (r, _) -> short r.resolvedPath) + |> List.choose (fun (name, n) -> if n > 1 then Some name else None) + |> fun names -> HashSet<_>(names, ic) + + let assemblies = + Dictionary(ic) + + let shareable = HashSet<_>(ic) + + for r, data in all do + let name = short r.resolvedPath + let refs, isMultiModule = reachableAssemblyNames name data + + assemblies[name] <- + { + File = fileIdentity r + References = refs + } + + // A project's own output changes every build; phase2 mutates a type provider's contents + if + r.ProjectReference.IsNone + && not isMultiModule + && not (ambiguous.Contains name) + && not (isTypeProviderAssembly data) + then + shareable.Add name |> ignore + + // A reference must resolve to what the key pins - this batch or the framework layer - or to + // nothing anywhere. One only an earlier batch resolves would be keyed as unresolved. + let pinnedByKey = Dictionary<_, bool>(ic) + + let isPinnedByKey ref = + match pinnedByKey.TryGetValue ref with + | true, v -> v + | _ -> + let resolvesIn (t: TcImports) = + match t.FindCcu(ctok, range0, ref, lookupOnly = true) with + | ResolvedCcu _ -> true + | UnresolvedCcu _ -> false + + let v = + assemblies.ContainsKey ref + || (match importsBase with + | Some b -> resolvesIn b + | None -> false) + || not (resolvesIn tcImports) + + pinnedByKey[ref] <- v + v + + for name in List.ofSeq shareable do + if not (assemblies[name].References |> List.forall isPinnedByKey) then + shareable.Remove name |> ignore + + SharedImportedCcus.computeKeys frameworkStamp assemblies shareable.Contains + + let contexts = ResizeArray() + + let registerDll + (keys: Dictionary option) + (r: AssemblyResolution, assemblyData: IRawFSharpAssemblyData) + = let m = r.originalReference.Range let fileName = r.resolvedPath let ilShortAssemName = assemblyData.ShortAssemblyName let ilScopeRef = assemblyData.ILScopeRef + // A project's own output can share a simple name with a package, so it is excluded here + let shared = + match keys with + | Some keys when r.ProjectReference.IsNone -> + match keys.TryGetValue(Path.GetFileNameWithoutExtension fileName) with + | true, shareable -> + let ctx = SharedImportedCcus.SharedImportContext(frameworkLayer, shareable.Closure) + + contexts.Add ctx + + let import: SharedImportedCcus.SharedImport = { Key = shareable.Key; Context = ctx } + + Some import + | _ -> None + | _ -> None + if tcImports.IsAlreadyRegistered ilShortAssemName then let phase2 () = @@ -2322,14 +2699,14 @@ and [] TcImports if assemblyData.HasAnyFSharpSignatureDataAttribute then if not assemblyData.HasMatchingFSharpSignatureDataAttribute then errorR (Error(FSComp.SR.buildDifferentVersionMustRecompile fileName, m)) - tcImports.PrepareToImportReferencedILAssembly(ctok, m, fileName, dllinfo) + tcImports.PrepareToImportReferencedILAssembly(ctok, m, fileName, dllinfo, ?shared = shared) else try - tcImports.PrepareToImportReferencedFSharpAssembly(ctok, m, fileName, dllinfo) + tcImports.PrepareToImportReferencedFSharpAssembly(ctok, m, fileName, dllinfo, ?shared = shared) with e -> error (Error(FSComp.SR.buildErrorOpeningBinaryFile (fileName, e.Message), m)) else - tcImports.PrepareToImportReferencedILAssembly(ctok, m, fileName, dllinfo) + tcImports.PrepareToImportReferencedILAssembly(ctok, m, fileName, dllinfo, ?shared = shared) async { return phase2 () } @@ -2346,12 +2723,42 @@ and [] TcImports let! assemblyData = nms |> List.map tryGetAssemblyData |> runMethod // Preserve determinicstic order of references, because types from later assemblies may shadow earlier ones. - let phase2s = assemblyData |> Seq.choose id |> Seq.map registerDll |> List.ofSeq + let resolved = assemblyData |> Seq.choose id |> List.ofSeq + + let keys = + // A framework base exists only for project layers, which register their whole set at once; + // BuildFrameworkTcImports uses three batches, and an early entry could not resolve the rest. + // + // Only with reduceMemoryUsage: otherwise disposal closes the reader a surviving ccu needs. + // + // The config check is what makes the stamp enough to pin the config + if + tcConfig.shareImportedAssemblies + && importsBase.IsSome + && tcConfig.reduceMemoryUsage = ReduceMemoryFlag.Yes + && importsBase.Value.GetImportReuseKey ctok = tcConfig.importReuseKey + then + Some(sharedKeys resolved) + else + None + + let phase2s = resolved |> List.map (registerDll keys) fixupOrphanCcus () let! ccuinfos = phase2s |> runMethod + // Everything is registered and no import forced yet, so no entry can be taken half-filled + for ctx in contexts do + if ctx.Pending.Count > 0 then + for name in ctx.Closure do + match tcImports.FindCcu(ctok, range0, name, lookupOnly = true) with + | ResolvedCcu ccu -> ctx.AddClosureRef(name, ccu) + | UnresolvedCcu _ -> () + + for key, ccu in ctx.Pending do + SharedImportedCcus.add key ccu + if importsBase.IsSome then let addConstraintSources (ia: ImportedAssembly) = // Only an F# assembly can carry a trait constraint to label. diff --git a/src/Compiler/Driver/CompilerImports.fsi b/src/Compiler/Driver/CompilerImports.fsi index 9da0ef71b1d..5cf0f846e25 100644 --- a/src/Compiler/Driver/CompilerImports.fsi +++ b/src/Compiler/Driver/CompilerImports.fsi @@ -98,6 +98,12 @@ type ResolvedExtensionReference = | ResolvedExtensionReference of string * AssemblyReference list * Tainted list #endif +/// Shares one copy of an assembly's Entity graph between the projects resolving that assembly, and +/// everything it can reach, to the same files. Entries are weak: nothing is retained on a project's behalf. +module internal SharedImportedCcus = + + val clear: unit -> unit + /// Represents a resolved imported binary [] type ImportedBinary = diff --git a/src/Compiler/Service/BackgroundCompiler.fs b/src/Compiler/Service/BackgroundCompiler.fs index 9e0e32bd7e7..50f9881260f 100644 --- a/src/Compiler/Service/BackgroundCompiler.fs +++ b/src/Compiler/Service/BackgroundCompiler.fs @@ -245,6 +245,7 @@ type internal BackgroundCompiler enableBackgroundItemKeyStoreAndSemanticClassification, enablePartialTypeChecking, parallelReferenceResolution, + shareImportedAssemblies, captureIdentifiersWhenParsing, getSource: (string -> Async) option, useChangeNotifications @@ -382,6 +383,7 @@ type internal BackgroundCompiler enablePartialTypeChecking, dependencyProvider, parallelReferenceResolution, + shareImportedAssemblies, captureIdentifiersWhenParsing, getSource, useChangeNotifications diff --git a/src/Compiler/Service/BackgroundCompiler.fsi b/src/Compiler/Service/BackgroundCompiler.fsi index 30c008c2ed5..fe098cd22be 100644 --- a/src/Compiler/Service/BackgroundCompiler.fsi +++ b/src/Compiler/Service/BackgroundCompiler.fsi @@ -232,6 +232,7 @@ type internal BackgroundCompiler = enableBackgroundItemKeyStoreAndSemanticClassification: bool * enablePartialTypeChecking: bool * parallelReferenceResolution: ParallelReferenceResolution * + shareImportedAssemblies: bool * captureIdentifiersWhenParsing: bool * getSource: (string -> Async) option * useChangeNotifications: bool -> diff --git a/src/Compiler/Service/IncrementalBuild.fs b/src/Compiler/Service/IncrementalBuild.fs index 48a42e66ff9..f3dc5086fe4 100644 --- a/src/Compiler/Service/IncrementalBuild.fs +++ b/src/Compiler/Service/IncrementalBuild.fs @@ -488,14 +488,14 @@ type BoundModel private ( /// Global service state type FrameworkImportsCacheKey = - | FrameworkImportsCacheKey of resolvedpath: string list * assemblyName: string * targetFrameworkDirectories: string list * fsharpBinaries: string * langVersion: decimal * checkNulls: bool + | FrameworkImportsCacheKey of resolvedpath: string list * assemblyName: string * targetFrameworkDirectories: string list * fsharpBinaries: string * importReuseKey: ImportReuseKey interface ICacheKey with member this.GetKey() = - this |> function FrameworkImportsCacheKey(assemblyName=a;checkNulls=c) -> if c then a + "CheckNulls" else a + this |> function FrameworkImportsCacheKey(assemblyName=a;importReuseKey=c) -> if c.CheckNullness then a + "CheckNulls" else a member this.GetLabel() = - this |> function FrameworkImportsCacheKey(assemblyName=a;checkNulls=c) -> if c then a + "CheckNulls" else a + this |> function FrameworkImportsCacheKey(assemblyName=a;importReuseKey=c) -> if c.CheckNullness then a + "CheckNulls" else a member this.GetVersion() = this @@ -532,8 +532,7 @@ type FrameworkImportsCache(size) = tcConfig.primaryAssembly.Name, tcConfig.GetTargetFrameworkDirectories(), tcConfig.fsharpBinariesDir, - tcConfig.langVersion.SpecifiedVersion, - tcConfig.checkNullness) + tcConfig.importReuseKey) let node = lock gate (fun () -> @@ -1432,6 +1431,7 @@ type IncrementalBuilder(initialState: IncrementalBuilderInitialState, state: Inc enablePartialTypeChecking, dependencyProvider, parallelReferenceResolution, + shareImportedAssemblies, captureIdentifiersWhenParsing, getSource, useChangeNotifications @@ -1518,6 +1518,7 @@ type IncrementalBuilder(initialState: IncrementalBuilderInitialState, state: Inc |> Some tcConfigB.parallelReferenceResolution <- parallelReferenceResolution + tcConfigB.shareImportedAssemblies <- shareImportedAssemblies tcConfigB.captureIdentifiersWhenParsing <- captureIdentifiersWhenParsing tcConfigB, sourceFilesNew diff --git a/src/Compiler/Service/IncrementalBuild.fsi b/src/Compiler/Service/IncrementalBuild.fsi index cced43ea44b..03c37da8216 100644 --- a/src/Compiler/Service/IncrementalBuild.fsi +++ b/src/Compiler/Service/IncrementalBuild.fsi @@ -28,8 +28,7 @@ type internal FrameworkImportsCacheKey = assemblyName: string * targetFrameworkDirectories: string list * fsharpBinaries: string * - langVersion: decimal * - checkNulls: bool + importReuseKey: ImportReuseKey interface ICacheKey @@ -291,6 +290,7 @@ type internal IncrementalBuilder = enablePartialTypeChecking: bool * dependencyProvider: DependencyProvider option * parallelReferenceResolution: ParallelReferenceResolution * + shareImportedAssemblies: bool * captureIdentifiersWhenParsing: bool * getSource: (string -> Async) option * useChangeNotifications: bool -> diff --git a/src/Compiler/Service/TransparentCompiler.fs b/src/Compiler/Service/TransparentCompiler.fs index 4666aa930ed..691dde3e802 100644 --- a/src/Compiler/Service/TransparentCompiler.fs +++ b/src/Compiler/Service/TransparentCompiler.fs @@ -419,6 +419,7 @@ type internal TransparentCompiler enableBackgroundItemKeyStoreAndSemanticClassification, enablePartialTypeChecking, parallelReferenceResolution, + shareImportedAssemblies, captureIdentifiersWhenParsing, getSource: (string -> Async) option, useChangeNotifications, @@ -469,6 +470,7 @@ type internal TransparentCompiler enableBackgroundItemKeyStoreAndSemanticClassification, enablePartialTypeChecking, parallelReferenceResolution, + shareImportedAssemblies, captureIdentifiersWhenParsing, getSource, useChangeNotifications @@ -612,8 +614,7 @@ type internal TransparentCompiler tcConfig.primaryAssembly.Name, tcConfig.GetTargetFrameworkDirectories(), tcConfig.fsharpBinariesDir, - tcConfig.langVersion.SpecifiedVersion, - tcConfig.checkNullness + tcConfig.importReuseKey ) caches.FrameworkImports.Get( @@ -931,6 +932,7 @@ type internal TransparentCompiler |> Some tcConfigB.parallelReferenceResolution <- parallelReferenceResolution + tcConfigB.shareImportedAssemblies <- shareImportedAssemblies tcConfigB.captureIdentifiersWhenParsing <- captureIdentifiersWhenParsing return tcConfigB, sourceFilesNew, loadClosureOpt diff --git a/src/Compiler/Service/TransparentCompiler.fsi b/src/Compiler/Service/TransparentCompiler.fsi index 7e947fe81e9..2d1c3506f0b 100644 --- a/src/Compiler/Service/TransparentCompiler.fsi +++ b/src/Compiler/Service/TransparentCompiler.fsi @@ -190,6 +190,7 @@ type internal TransparentCompiler = enableBackgroundItemKeyStoreAndSemanticClassification: bool * enablePartialTypeChecking: bool * parallelReferenceResolution: ParallelReferenceResolution * + shareImportedAssemblies: bool * captureIdentifiersWhenParsing: bool * getSource: (string -> Async) option * useChangeNotifications: bool * diff --git a/src/Compiler/Service/service.fs b/src/Compiler/Service/service.fs index 1006def6da1..a07f5f90e4e 100644 --- a/src/Compiler/Service/service.fs +++ b/src/Compiler/Service/service.fs @@ -11,6 +11,7 @@ open FSharp.Compiler.Caches open FSharp.Compiler.CodeAnalysis open FSharp.Compiler.CodeAnalysis.TransparentCompiler open FSharp.Compiler.CompilerConfig +open FSharp.Compiler.CompilerImports open FSharp.Compiler.CompilerOptions open FSharp.Compiler.Diagnostics open FSharp.Compiler.Driver @@ -97,6 +98,7 @@ type FSharpChecker enableBackgroundItemKeyStoreAndSemanticClassification, enablePartialTypeChecking, parallelReferenceResolution, + shareImportedAssemblies, captureIdentifiersWhenParsing, getSource, useChangeNotifications, @@ -117,6 +119,7 @@ type FSharpChecker enableBackgroundItemKeyStoreAndSemanticClassification, enablePartialTypeChecking, parallelReferenceResolution, + shareImportedAssemblies, captureIdentifiersWhenParsing, getSource, useChangeNotifications, @@ -135,6 +138,7 @@ type FSharpChecker enableBackgroundItemKeyStoreAndSemanticClassification, enablePartialTypeChecking, parallelReferenceResolution, + shareImportedAssemblies, captureIdentifiersWhenParsing, getSource, useChangeNotifications @@ -181,6 +185,7 @@ type FSharpChecker ?enableBackgroundItemKeyStoreAndSemanticClassification, ?enablePartialTypeChecking, ?parallelReferenceResolution: bool, + ?shareImportedAssemblies: bool, ?captureIdentifiersWhenParsing: bool, ?documentSource: DocumentSource, ?useTransparentCompiler: bool, @@ -215,6 +220,8 @@ type FSharpChecker if keepAssemblyContents && enablePartialTypeChecking then invalidArg "enablePartialTypeChecking" "'keepAssemblyContents' and 'enablePartialTypeChecking' cannot be both enabled." + let shareImportedAssemblies = defaultArg shareImportedAssemblies true + let parallelReferenceResolution = inferParallelReferenceResolution parallelReferenceResolution FSharpChecker( @@ -228,6 +235,7 @@ type FSharpChecker enableBackgroundItemKeyStoreAndSemanticClassification, enablePartialTypeChecking, parallelReferenceResolution, + shareImportedAssemblies, captureIdentifiersWhenParsing, (match documentSource with | Some(DocumentSource.Custom f) -> Some f @@ -330,6 +338,7 @@ type FSharpChecker braceMatchCache.Clear(utok) backgroundCompiler.ClearCaches() ClearAllILModuleReaderCache() + SharedImportedCcus.clear () member ic.ClearLanguageServiceRootCachesAndCollectAndFinalizeAllTransients() = use _ = diff --git a/src/Compiler/Service/service.fsi b/src/Compiler/Service/service.fsi index 1584e19562b..40410c4a004 100644 --- a/src/Compiler/Service/service.fsi +++ b/src/Compiler/Service/service.fsi @@ -32,6 +32,7 @@ type public FSharpChecker = /// 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 keepAssemblyContents 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. /// Indicates whether to resolve references in parallel. + /// Default: true. Indicates whether the contents imported from a referenced assembly may be shared between projects that resolve that assembly, and everything it can reach, to the same files. Saves memory and import time in solutions where projects reference the same binaries, and costs nothing where they do not. Assemblies containing type providers, and assemblies produced by a project in the same solution, are never shared. Set to false to opt out. /// When set to true we create a set of all identifiers for each parsed file which can be used to speed up finding references. /// Default: FileSystem. You can use Custom source to provide a function that will return the source for a given file path instead of reading it from the file system. Note that with this option the FSharpChecker will also not monitor the file system for file changes. It will expect to be notified of changes via the NotifyFileChanged method. /// Default: false. Indicates whether we use a new experimental background compiler. This does not yet support all features @@ -47,6 +48,7 @@ type public FSharpChecker = ?enableBackgroundItemKeyStoreAndSemanticClassification: bool * ?enablePartialTypeChecking: bool * ?parallelReferenceResolution: bool * + ?shareImportedAssemblies: bool * ?captureIdentifiersWhenParsing: bool * [] ?documentSource: DocumentSource * diff --git a/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.bsl b/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.bsl index 67f00686a6d..5c9c346b613 100644 --- a/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.bsl +++ b/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.bsl @@ -2161,7 +2161,7 @@ FSharp.Compiler.CodeAnalysis.FSharpCheckProjectResults: System.String[] Dependen FSharp.Compiler.CodeAnalysis.FSharpCheckProjectResults: System.String[] get_DependencyFiles() FSharp.Compiler.CodeAnalysis.FSharpChecker: Boolean UsesTransparentCompiler FSharp.Compiler.CodeAnalysis.FSharpChecker: Boolean get_UsesTransparentCompiler() -FSharp.Compiler.CodeAnalysis.FSharpChecker: FSharp.Compiler.CodeAnalysis.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.CodeAnalysis.LegacyReferenceResolver], 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], Microsoft.FSharp.Core.FSharpOption`1[System.Boolean], Microsoft.FSharp.Core.FSharpOption`1[System.Boolean], Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.CodeAnalysis.DocumentSource], Microsoft.FSharp.Core.FSharpOption`1[System.Boolean], Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.CodeAnalysis.TransparentCompiler.CacheSizes]) +FSharp.Compiler.CodeAnalysis.FSharpChecker: FSharp.Compiler.CodeAnalysis.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.CodeAnalysis.LegacyReferenceResolver], 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], 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[FSharp.Compiler.CodeAnalysis.DocumentSource], Microsoft.FSharp.Core.FSharpOption`1[System.Boolean], Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.CodeAnalysis.TransparentCompiler.CacheSizes]) FSharp.Compiler.CodeAnalysis.FSharpChecker: FSharp.Compiler.CodeAnalysis.FSharpChecker Instance FSharp.Compiler.CodeAnalysis.FSharpChecker: FSharp.Compiler.CodeAnalysis.FSharpChecker get_Instance() FSharp.Compiler.CodeAnalysis.FSharpChecker: FSharp.Compiler.CodeAnalysis.FSharpProjectOptions GetProjectOptionsFromCommandLineArgs(System.String, System.String[], Microsoft.FSharp.Core.FSharpOption`1[System.DateTime], Microsoft.FSharp.Core.FSharpOption`1[System.Boolean], Microsoft.FSharp.Core.FSharpOption`1[System.Boolean]) @@ -5672,7 +5672,6 @@ FSharp.Compiler.Symbols.FSharpMemberOrFunctionOrValue: Boolean EventIsStandard FSharp.Compiler.Symbols.FSharpMemberOrFunctionOrValue: Boolean HasGetterMethod FSharp.Compiler.Symbols.FSharpMemberOrFunctionOrValue: Boolean HasSetterMethod FSharp.Compiler.Symbols.FSharpMemberOrFunctionOrValue: Boolean HasSignatureFile -FSharp.Compiler.Symbols.FSharpMemberOrFunctionOrValue: Boolean IsPropertyAccessor FSharp.Compiler.Symbols.FSharpMemberOrFunctionOrValue: Boolean IsActivePattern FSharp.Compiler.Symbols.FSharpMemberOrFunctionOrValue: Boolean IsBaseValue FSharp.Compiler.Symbols.FSharpMemberOrFunctionOrValue: Boolean IsCompilerGenerated @@ -5695,6 +5694,7 @@ FSharp.Compiler.Symbols.FSharpMemberOrFunctionOrValue: Boolean IsModuleValueOrMe FSharp.Compiler.Symbols.FSharpMemberOrFunctionOrValue: Boolean IsMutable FSharp.Compiler.Symbols.FSharpMemberOrFunctionOrValue: Boolean IsOverrideOrExplicitInterfaceImplementation FSharp.Compiler.Symbols.FSharpMemberOrFunctionOrValue: Boolean IsProperty +FSharp.Compiler.Symbols.FSharpMemberOrFunctionOrValue: Boolean IsPropertyAccessor FSharp.Compiler.Symbols.FSharpMemberOrFunctionOrValue: Boolean IsPropertyGetterMethod FSharp.Compiler.Symbols.FSharpMemberOrFunctionOrValue: Boolean IsPropertySetterMethod FSharp.Compiler.Symbols.FSharpMemberOrFunctionOrValue: Boolean IsRefCell @@ -5708,7 +5708,6 @@ FSharp.Compiler.Symbols.FSharpMemberOrFunctionOrValue: Boolean get_EventIsStanda FSharp.Compiler.Symbols.FSharpMemberOrFunctionOrValue: Boolean get_HasGetterMethod() FSharp.Compiler.Symbols.FSharpMemberOrFunctionOrValue: Boolean get_HasSetterMethod() FSharp.Compiler.Symbols.FSharpMemberOrFunctionOrValue: Boolean get_HasSignatureFile() -FSharp.Compiler.Symbols.FSharpMemberOrFunctionOrValue: Boolean get_IsPropertyAccessor() FSharp.Compiler.Symbols.FSharpMemberOrFunctionOrValue: Boolean get_IsActivePattern() FSharp.Compiler.Symbols.FSharpMemberOrFunctionOrValue: Boolean get_IsBaseValue() FSharp.Compiler.Symbols.FSharpMemberOrFunctionOrValue: Boolean get_IsCompilerGenerated() @@ -5731,6 +5730,7 @@ FSharp.Compiler.Symbols.FSharpMemberOrFunctionOrValue: Boolean get_IsModuleValue FSharp.Compiler.Symbols.FSharpMemberOrFunctionOrValue: Boolean get_IsMutable() FSharp.Compiler.Symbols.FSharpMemberOrFunctionOrValue: Boolean get_IsOverrideOrExplicitInterfaceImplementation() FSharp.Compiler.Symbols.FSharpMemberOrFunctionOrValue: Boolean get_IsProperty() +FSharp.Compiler.Symbols.FSharpMemberOrFunctionOrValue: Boolean get_IsPropertyAccessor() FSharp.Compiler.Symbols.FSharpMemberOrFunctionOrValue: Boolean get_IsPropertyGetterMethod() FSharp.Compiler.Symbols.FSharpMemberOrFunctionOrValue: Boolean get_IsPropertySetterMethod() FSharp.Compiler.Symbols.FSharpMemberOrFunctionOrValue: Boolean get_IsRefCell() @@ -11339,8 +11339,8 @@ FSharp.Compiler.Text.TextTag+Tags: Int32 TypeParameter FSharp.Compiler.Text.TextTag+Tags: Int32 Union FSharp.Compiler.Text.TextTag+Tags: Int32 UnionCase FSharp.Compiler.Text.TextTag+Tags: Int32 UnknownEntity -FSharp.Compiler.Text.TextTag+Tags: Int32 UnresolvedName FSharp.Compiler.Text.TextTag+Tags: Int32 UnknownType +FSharp.Compiler.Text.TextTag+Tags: Int32 UnresolvedName FSharp.Compiler.Text.TextTag: Boolean Equals(FSharp.Compiler.Text.TextTag) FSharp.Compiler.Text.TextTag: Boolean Equals(FSharp.Compiler.Text.TextTag, System.Collections.IEqualityComparer) FSharp.Compiler.Text.TextTag: Boolean Equals(System.Object) @@ -11378,8 +11378,8 @@ FSharp.Compiler.Text.TextTag: Boolean IsTypeParameter FSharp.Compiler.Text.TextTag: Boolean IsUnion FSharp.Compiler.Text.TextTag: Boolean IsUnionCase FSharp.Compiler.Text.TextTag: Boolean IsUnknownEntity -FSharp.Compiler.Text.TextTag: Boolean IsUnresolvedName FSharp.Compiler.Text.TextTag: Boolean IsUnknownType +FSharp.Compiler.Text.TextTag: Boolean IsUnresolvedName FSharp.Compiler.Text.TextTag: Boolean get_IsActivePatternCase() FSharp.Compiler.Text.TextTag: Boolean get_IsActivePatternResult() FSharp.Compiler.Text.TextTag: Boolean get_IsAlias() @@ -11413,8 +11413,8 @@ FSharp.Compiler.Text.TextTag: Boolean get_IsTypeParameter() FSharp.Compiler.Text.TextTag: Boolean get_IsUnion() FSharp.Compiler.Text.TextTag: Boolean get_IsUnionCase() FSharp.Compiler.Text.TextTag: Boolean get_IsUnknownEntity() -FSharp.Compiler.Text.TextTag: Boolean get_IsUnresolvedName() FSharp.Compiler.Text.TextTag: Boolean get_IsUnknownType() +FSharp.Compiler.Text.TextTag: Boolean get_IsUnresolvedName() FSharp.Compiler.Text.TextTag: FSharp.Compiler.Text.TextTag ActivePatternCase FSharp.Compiler.Text.TextTag: FSharp.Compiler.Text.TextTag ActivePatternResult FSharp.Compiler.Text.TextTag: FSharp.Compiler.Text.TextTag Alias @@ -11448,8 +11448,8 @@ FSharp.Compiler.Text.TextTag: FSharp.Compiler.Text.TextTag TypeParameter FSharp.Compiler.Text.TextTag: FSharp.Compiler.Text.TextTag Union FSharp.Compiler.Text.TextTag: FSharp.Compiler.Text.TextTag UnionCase FSharp.Compiler.Text.TextTag: FSharp.Compiler.Text.TextTag UnknownEntity -FSharp.Compiler.Text.TextTag: FSharp.Compiler.Text.TextTag UnresolvedName FSharp.Compiler.Text.TextTag: FSharp.Compiler.Text.TextTag UnknownType +FSharp.Compiler.Text.TextTag: FSharp.Compiler.Text.TextTag UnresolvedName FSharp.Compiler.Text.TextTag: FSharp.Compiler.Text.TextTag get_ActivePatternCase() FSharp.Compiler.Text.TextTag: FSharp.Compiler.Text.TextTag get_ActivePatternResult() FSharp.Compiler.Text.TextTag: FSharp.Compiler.Text.TextTag get_Alias() @@ -11483,8 +11483,8 @@ FSharp.Compiler.Text.TextTag: FSharp.Compiler.Text.TextTag get_TypeParameter() FSharp.Compiler.Text.TextTag: FSharp.Compiler.Text.TextTag get_Union() FSharp.Compiler.Text.TextTag: FSharp.Compiler.Text.TextTag get_UnionCase() FSharp.Compiler.Text.TextTag: FSharp.Compiler.Text.TextTag get_UnknownEntity() -FSharp.Compiler.Text.TextTag: FSharp.Compiler.Text.TextTag get_UnresolvedName() FSharp.Compiler.Text.TextTag: FSharp.Compiler.Text.TextTag get_UnknownType() +FSharp.Compiler.Text.TextTag: FSharp.Compiler.Text.TextTag get_UnresolvedName() FSharp.Compiler.Text.TextTag: FSharp.Compiler.Text.TextTag+Tags FSharp.Compiler.Text.TextTag: Int32 GetHashCode() FSharp.Compiler.Text.TextTag: Int32 GetHashCode(System.Collections.IEqualityComparer) @@ -12734,4 +12734,4 @@ Internal.Utilities.Library.InterruptibleLazy`1[T]: Internal.Utilities.Library.In Internal.Utilities.Library.InterruptibleLazy`1[T]: T Force() Internal.Utilities.Library.InterruptibleLazy`1[T]: T Value Internal.Utilities.Library.InterruptibleLazy`1[T]: T get_Value() -Internal.Utilities.Library.InterruptibleLazy`1[T]: Void .ctor(Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.Unit,T]) +Internal.Utilities.Library.InterruptibleLazy`1[T]: Void .ctor(Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.Unit,T]) \ No newline at end of file