From a2f162738ac957d96c68256be401aca02985f1a3 Mon Sep 17 00:00:00 2001 From: dawe Date: Wed, 31 Jan 2024 15:15:49 +0100 Subject: [PATCH 01/11] bring in the scriptClosureCache to the TransparentCompiler --- src/Compiler/Service/TransparentCompiler.fs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/Compiler/Service/TransparentCompiler.fs b/src/Compiler/Service/TransparentCompiler.fs index 298c0e6b627..6027fe4a492 100644 --- a/src/Compiler/Service/TransparentCompiler.fs +++ b/src/Compiler/Service/TransparentCompiler.fs @@ -346,6 +346,14 @@ type internal TransparentCompiler let fileChecked = Event() let projectChecked = Event() + /// Information about the derived script closure. + let scriptClosureCache = + MruCache( + projectCacheSize, + areSame = FSharpProjectOptions.AreSameForChecking, + areSimilar = FSharpProjectOptions.UseSameProject + ) + // use this to process not-yet-implemented tasks let backgroundCompiler = BackgroundCompiler( @@ -551,8 +559,8 @@ type internal TransparentCompiler let projectReferences = getProjectReferences projectSnapshot "ComputeTcConfigBuilder" - // TODO: script support - let loadClosureOpt: LoadClosure option = None + let loadClosureOpt: LoadClosure option = + scriptClosureCache.TryGet(AnyCallerThread, projectSnapshot.ToOptions()) let getSwitchValue (switchString: string) = match commandLineArgs |> List.tryFindIndex (fun s -> s.StartsWithOrdinal switchString) with @@ -1408,7 +1416,8 @@ type internal TransparentCompiler let tcDiagnostics = [| yield! creationDiags; yield! extraDiagnostics; yield! tcDiagnostics |] - let loadClosure = None // TODO: script support + let loadClosure = + scriptClosureCache.TryGet(AnyCallerThread, projectSnapshot.ToOptions()) let typedResults = FSharpCheckFileResults.Make( From adfe73079d7f0fd08c86ec16e12bf3413668af30 Mon Sep 17 00:00:00 2001 From: dawe Date: Wed, 31 Jan 2024 17:03:13 +0100 Subject: [PATCH 02/11] given that we need to clear and resize the ScriptClosureCache under a lock, it might be a better approach to just use the one from the BackgroundCompiler --- src/Compiler/Service/BackgroundCompiler.fs | 4 ++++ src/Compiler/Service/BackgroundCompiler.fsi | 5 +++++ src/Compiler/Service/TransparentCompiler.fs | 14 ++++---------- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/Compiler/Service/BackgroundCompiler.fs b/src/Compiler/Service/BackgroundCompiler.fs index f9f952dde70..d6107e4e05d 100644 --- a/src/Compiler/Service/BackgroundCompiler.fs +++ b/src/Compiler/Service/BackgroundCompiler.fs @@ -173,6 +173,8 @@ type internal IBackgroundCompiler = abstract member ProjectChecked: IEvent + abstract member ScriptClosureCache: MruCache + type internal ParseCacheLockToken() = interface LockToken @@ -1678,3 +1680,5 @@ type internal BackgroundCompiler userOpName: string ) : (FSharpParseFileResults * FSharpCheckFileResults * SourceTextHash) option = self.TryGetRecentCheckResultsForFile(fileName, options, sourceText, userOpName) + + member _.ScriptClosureCache = scriptClosureCache diff --git a/src/Compiler/Service/BackgroundCompiler.fsi b/src/Compiler/Service/BackgroundCompiler.fsi index f3bf3c96ccc..121559497d7 100644 --- a/src/Compiler/Service/BackgroundCompiler.fsi +++ b/src/Compiler/Service/BackgroundCompiler.fsi @@ -1,5 +1,6 @@ namespace FSharp.Compiler.CodeAnalysis +open FSharp.Compiler.ScriptClosure open FSharp.Compiler.Text open FSharp.Compiler.BuildGraph @@ -7,6 +8,8 @@ open System.Reflection open FSharp.Compiler.CodeAnalysis open FSharp.Compiler.CompilerConfig open FSharp.Compiler.Diagnostics +open Internal.Utilities.Collections +open Internal.Utilities.Library type SourceTextHash = int64 @@ -163,6 +166,8 @@ type internal IBackgroundCompiler = abstract ProjectChecked: IEvent + abstract ScriptClosureCache: MruCache + [] module internal EnvMisc = diff --git a/src/Compiler/Service/TransparentCompiler.fs b/src/Compiler/Service/TransparentCompiler.fs index 6027fe4a492..fcb8703383c 100644 --- a/src/Compiler/Service/TransparentCompiler.fs +++ b/src/Compiler/Service/TransparentCompiler.fs @@ -346,14 +346,6 @@ type internal TransparentCompiler let fileChecked = Event() let projectChecked = Event() - /// Information about the derived script closure. - let scriptClosureCache = - MruCache( - projectCacheSize, - areSame = FSharpProjectOptions.AreSameForChecking, - areSimilar = FSharpProjectOptions.UseSameProject - ) - // use this to process not-yet-implemented tasks let backgroundCompiler = BackgroundCompiler( @@ -560,7 +552,7 @@ type internal TransparentCompiler getProjectReferences projectSnapshot "ComputeTcConfigBuilder" let loadClosureOpt: LoadClosure option = - scriptClosureCache.TryGet(AnyCallerThread, projectSnapshot.ToOptions()) + backgroundCompiler.ScriptClosureCache.TryGet(AnyCallerThread, projectSnapshot.ToOptions()) let getSwitchValue (switchString: string) = match commandLineArgs |> List.tryFindIndex (fun s -> s.StartsWithOrdinal switchString) with @@ -1417,7 +1409,7 @@ type internal TransparentCompiler [| yield! creationDiags; yield! extraDiagnostics; yield! tcDiagnostics |] let loadClosure = - scriptClosureCache.TryGet(AnyCallerThread, projectSnapshot.ToOptions()) + backgroundCompiler.ScriptClosureCache.TryGet(AnyCallerThread, projectSnapshot.ToOptions()) let typedResults = FSharpCheckFileResults.Make( @@ -2078,3 +2070,5 @@ type internal TransparentCompiler userOpName: string ) : (FSharpParseFileResults * FSharpCheckFileResults * SourceTextHash) option = backgroundCompiler.TryGetRecentCheckResultsForFile(fileName, options, sourceText, userOpName) + + member _.ScriptClosureCache = backgroundCompiler.ScriptClosureCache From 5c143c06db67efbfb9e8c198406e7a63caff62ff Mon Sep 17 00:00:00 2001 From: dawe Date: Thu, 1 Feb 2024 17:54:09 +0100 Subject: [PATCH 03/11] Revert "given that we need to clear and resize the ScriptClosureCache under a lock, it might be a better approach to just use the one from the BackgroundCompiler" This reverts commit adfe73079d7f0fd08c86ec16e12bf3413668af30. --- src/Compiler/Service/BackgroundCompiler.fs | 4 ---- src/Compiler/Service/BackgroundCompiler.fsi | 5 ----- src/Compiler/Service/TransparentCompiler.fs | 14 ++++++++++---- 3 files changed, 10 insertions(+), 13 deletions(-) diff --git a/src/Compiler/Service/BackgroundCompiler.fs b/src/Compiler/Service/BackgroundCompiler.fs index d6107e4e05d..f9f952dde70 100644 --- a/src/Compiler/Service/BackgroundCompiler.fs +++ b/src/Compiler/Service/BackgroundCompiler.fs @@ -173,8 +173,6 @@ type internal IBackgroundCompiler = abstract member ProjectChecked: IEvent - abstract member ScriptClosureCache: MruCache - type internal ParseCacheLockToken() = interface LockToken @@ -1680,5 +1678,3 @@ type internal BackgroundCompiler userOpName: string ) : (FSharpParseFileResults * FSharpCheckFileResults * SourceTextHash) option = self.TryGetRecentCheckResultsForFile(fileName, options, sourceText, userOpName) - - member _.ScriptClosureCache = scriptClosureCache diff --git a/src/Compiler/Service/BackgroundCompiler.fsi b/src/Compiler/Service/BackgroundCompiler.fsi index 121559497d7..f3bf3c96ccc 100644 --- a/src/Compiler/Service/BackgroundCompiler.fsi +++ b/src/Compiler/Service/BackgroundCompiler.fsi @@ -1,6 +1,5 @@ namespace FSharp.Compiler.CodeAnalysis -open FSharp.Compiler.ScriptClosure open FSharp.Compiler.Text open FSharp.Compiler.BuildGraph @@ -8,8 +7,6 @@ open System.Reflection open FSharp.Compiler.CodeAnalysis open FSharp.Compiler.CompilerConfig open FSharp.Compiler.Diagnostics -open Internal.Utilities.Collections -open Internal.Utilities.Library type SourceTextHash = int64 @@ -166,8 +163,6 @@ type internal IBackgroundCompiler = abstract ProjectChecked: IEvent - abstract ScriptClosureCache: MruCache - [] module internal EnvMisc = diff --git a/src/Compiler/Service/TransparentCompiler.fs b/src/Compiler/Service/TransparentCompiler.fs index fcb8703383c..6027fe4a492 100644 --- a/src/Compiler/Service/TransparentCompiler.fs +++ b/src/Compiler/Service/TransparentCompiler.fs @@ -346,6 +346,14 @@ type internal TransparentCompiler let fileChecked = Event() let projectChecked = Event() + /// Information about the derived script closure. + let scriptClosureCache = + MruCache( + projectCacheSize, + areSame = FSharpProjectOptions.AreSameForChecking, + areSimilar = FSharpProjectOptions.UseSameProject + ) + // use this to process not-yet-implemented tasks let backgroundCompiler = BackgroundCompiler( @@ -552,7 +560,7 @@ type internal TransparentCompiler getProjectReferences projectSnapshot "ComputeTcConfigBuilder" let loadClosureOpt: LoadClosure option = - backgroundCompiler.ScriptClosureCache.TryGet(AnyCallerThread, projectSnapshot.ToOptions()) + scriptClosureCache.TryGet(AnyCallerThread, projectSnapshot.ToOptions()) let getSwitchValue (switchString: string) = match commandLineArgs |> List.tryFindIndex (fun s -> s.StartsWithOrdinal switchString) with @@ -1409,7 +1417,7 @@ type internal TransparentCompiler [| yield! creationDiags; yield! extraDiagnostics; yield! tcDiagnostics |] let loadClosure = - backgroundCompiler.ScriptClosureCache.TryGet(AnyCallerThread, projectSnapshot.ToOptions()) + scriptClosureCache.TryGet(AnyCallerThread, projectSnapshot.ToOptions()) let typedResults = FSharpCheckFileResults.Make( @@ -2070,5 +2078,3 @@ type internal TransparentCompiler userOpName: string ) : (FSharpParseFileResults * FSharpCheckFileResults * SourceTextHash) option = backgroundCompiler.TryGetRecentCheckResultsForFile(fileName, options, sourceText, userOpName) - - member _.ScriptClosureCache = backgroundCompiler.ScriptClosureCache From 752ad6115be1882ccca2ab60c53048e2ac7c8b8f Mon Sep 17 00:00:00 2001 From: dawe Date: Fri, 2 Feb 2024 15:14:12 +0100 Subject: [PATCH 04/11] - Don't rely on BackgroundCompiler for ScriptClosure - Cache it via AsyncMemoize - very WIP --- src/Compiler/Service/TransparentCompiler.fs | 175 ++++++++++++++------ 1 file changed, 123 insertions(+), 52 deletions(-) diff --git a/src/Compiler/Service/TransparentCompiler.fs b/src/Compiler/Service/TransparentCompiler.fs index 6027fe4a492..843ea11c0fa 100644 --- a/src/Compiler/Service/TransparentCompiler.fs +++ b/src/Compiler/Service/TransparentCompiler.fs @@ -291,6 +291,8 @@ type internal CompilerCaches(sizeFactor: int) = member val ItemKeyStore = AsyncMemoize(sf, 2 * sf, name = "ItemKeyStore") + member val ScriptClosure = AsyncMemoize(sf, 2 * sf, name = "ScriptClosure") + member this.Clear(projects: Set) = let shouldClear project = projects |> Set.contains project @@ -303,6 +305,7 @@ type internal CompilerCaches(sizeFactor: int) = this.AssemblyData.Clear(shouldClear) this.SemanticClassification.Clear(snd >> shouldClear) this.ItemKeyStore.Clear(snd >> shouldClear) + this.ScriptClosure.Clear(snd >> shouldClear) // Todo check if correct predicate type internal TransparentCompiler ( @@ -346,14 +349,6 @@ type internal TransparentCompiler let fileChecked = Event() let projectChecked = Event() - /// Information about the derived script closure. - let scriptClosureCache = - MruCache( - projectCacheSize, - areSame = FSharpProjectOptions.AreSameForChecking, - areSimilar = FSharpProjectOptions.UseSameProject - ) - // use this to process not-yet-implemented tasks let backgroundCompiler = BackgroundCompiler( @@ -374,6 +369,53 @@ type internal TransparentCompiler ) :> IBackgroundCompiler + let ComputeScriptClosure + (fileName: string) + (source: ISourceText) + (defaultFSharpBinariesDir: string) + (useSimpleResolution: bool) + (useFsiAuxLib: bool option) + (useSdkRefs: bool option) + (sdkDirOverride: string option) + (assumeDotNetFramework: bool option) + (projectSnapshot: ProjectSnapshot) + = + caches.ScriptClosure.Get( + projectSnapshot.FileKey fileName, + node { + let useFsiAuxLib = defaultArg useFsiAuxLib true + let useSdkRefs = defaultArg useSdkRefs true + let reduceMemoryUsage = ReduceMemoryFlag.Yes + // Do we assume .NET Framework references for scripts? + let assumeDotNetFramework = defaultArg assumeDotNetFramework true + + let applyCompilerOptions tcConfig = + let fsiCompilerOptions = GetCoreFsiCompilerOptions tcConfig + ParseCompilerOptions(ignore, fsiCompilerOptions, projectSnapshot.OtherOptions) + + let closure = + LoadClosure.ComputeClosureOfScriptText( + legacyReferenceResolver, + defaultFSharpBinariesDir, + fileName, + source, + CodeContext.Editing, + useSimpleResolution, + useFsiAuxLib, + useSdkRefs, + sdkDirOverride, + Lexhelp.LexResourceManager(), + applyCompilerOptions, + assumeDotNetFramework, + tryGetMetadataSnapshot, + reduceMemoryUsage, + dependencyProviderForScripts + ) + + return closure + } + ) + let ComputeFrameworkImports (tcConfig: TcConfig) frameworkDLLs nonFrameworkResolutions = let frameworkDLLsKey = frameworkDLLs @@ -559,8 +601,7 @@ type internal TransparentCompiler let projectReferences = getProjectReferences projectSnapshot "ComputeTcConfigBuilder" - let loadClosureOpt: LoadClosure option = - scriptClosureCache.TryGet(AnyCallerThread, projectSnapshot.ToOptions()) + let loadClosureOpt: LoadClosure option = None // ToDo let getSwitchValue (switchString: string) = match commandLineArgs |> List.tryFindIndex (fun s -> s.StartsWithOrdinal switchString) with @@ -716,47 +757,68 @@ type internal TransparentCompiler } ) - let computeBootstrapInfoInner (projectSnapshot: ProjectSnapshot) = + let computeBootstrapInfoInner (fileName: string option) (projectSnapshot: ProjectSnapshot) = node { - let tcConfigB, sourceFiles, loadClosureOpt = ComputeTcConfigBuilder projectSnapshot + let tcConfigB, sourceFiles, _ = ComputeTcConfigBuilder projectSnapshot // If this is a builder for a script, re-apply the settings inferred from the // script and its load closure to the configuration. // // NOTE: it would probably be cleaner and more accurate to re-run the load closure at this point. let setupConfigFromLoadClosure () = - match loadClosureOpt with - | Some loadClosure -> - let dllReferences = - [ - for reference in tcConfigB.referencedDLLs do - // If there's (one or more) resolutions of closure references then yield them all - match - loadClosure.References - |> List.tryFind (fun (resolved, _) -> resolved = reference.Text) - with - | Some(resolved, closureReferences) -> - for closureReference in closureReferences do - yield AssemblyReference(closureReference.originalReference.Range, resolved, None) - | None -> yield reference - ] - - tcConfigB.referencedDLLs <- [] - - tcConfigB.primaryAssembly <- - (if loadClosure.UseDesktopFramework then - PrimaryAssembly.Mscorlib - else - PrimaryAssembly.System_Runtime) - // Add one by one to remove duplicates - dllReferences - |> List.iter (fun dllReference -> tcConfigB.AddReferencedAssemblyByPath(dllReference.Range, dllReference.Text)) - - tcConfigB.knownUnresolvedReferences <- loadClosure.UnresolvedReferences - | None -> () - - setupConfigFromLoadClosure () + match fileName, projectSnapshot.UseScriptResolutionRules with + | Some fileName, true -> + node { + let sourceFile = + projectSnapshot.SourceFiles |> List.find (fun x -> x.FileName = fileName) + + let! source = sourceFile.GetSource() |> NodeCode.AwaitTask + + let! loadClosure = + ComputeScriptClosure + fileName + source + tcConfigB.defaultFSharpBinariesDir + tcConfigB.useSimpleResolution + (Some tcConfigB.useFsiAuxLib) + (Some tcConfigB.useSdkRefs) + tcConfigB.sdkDirOverride + None + projectSnapshot + + let dllReferences = + [ + for reference in tcConfigB.referencedDLLs do + // If there's (one or more) resolutions of closure references then yield them all + match + loadClosure.References + |> List.tryFind (fun (resolved, _) -> resolved = reference.Text) + with + | Some(resolved, closureReferences) -> + for closureReference in closureReferences do + yield AssemblyReference(closureReference.originalReference.Range, resolved, None) + | None -> yield reference + ] + + tcConfigB.referencedDLLs <- [] + + tcConfigB.primaryAssembly <- + (if loadClosure.UseDesktopFramework then + PrimaryAssembly.Mscorlib + else + PrimaryAssembly.System_Runtime) + // Add one by one to remove duplicates + dllReferences + |> List.iter (fun dllReference -> tcConfigB.AddReferencedAssemblyByPath(dllReference.Range, dllReference.Text)) + + tcConfigB.knownUnresolvedReferences <- loadClosure.UnresolvedReferences + + return Some loadClosure + } + | _ -> node { return None } + + let! loadClosureOpt = setupConfigFromLoadClosure () let tcConfig = TcConfig.Create(tcConfigB, validate = true) let outFile, _, assemblyName = tcConfigB.DecideNames sourceFiles @@ -786,7 +848,7 @@ type internal TransparentCompiler } } - let ComputeBootstrapInfo (projectSnapshot: ProjectSnapshot) = + let ComputeBootstrapInfo (fileName: string option) (projectSnapshot: ProjectSnapshot) = caches.BootstrapInfo.Get( projectSnapshot.NoFileVersionsKey, @@ -801,7 +863,7 @@ type internal TransparentCompiler let! bootstrapInfoOpt = node { try - return! computeBootstrapInfoInner projectSnapshot + return! computeBootstrapInfoInner fileName projectSnapshot with exn -> errorRecoveryNoRange exn return None @@ -1352,7 +1414,7 @@ type internal TransparentCompiler use _ = Activity.start "ComputeParseAndCheckFileInProject" [| Activity.Tags.fileName, fileName |> Path.GetFileName |] - match! ComputeBootstrapInfo projectSnapshot with + match! ComputeBootstrapInfo (Some fileName) projectSnapshot with | None, creationDiags -> return emptyParseResult fileName creationDiags, FSharpCheckFileAnswer.Aborted | Some bootstrapInfo, creationDiags -> @@ -1416,8 +1478,17 @@ type internal TransparentCompiler let tcDiagnostics = [| yield! creationDiags; yield! extraDiagnostics; yield! tcDiagnostics |] - let loadClosure = - scriptClosureCache.TryGet(AnyCallerThread, projectSnapshot.ToOptions()) + let! loadClosure = + ComputeScriptClosure + fileName + file.Source + tcConfig.fsharpBinariesDir + tcConfig.useSimpleResolution + (Some tcConfig.useFsiAuxLib) + (Some tcConfig.useSdkRefs) + tcConfig.sdkDirOverride + (Some tcConfig.assumeDotNetFramework) + projectSnapshot let typedResults = FSharpCheckFileResults.Make( @@ -1440,7 +1511,7 @@ type internal TransparentCompiler tcResolutions, tcSymbolUses, tcEnv.NameEnv, - loadClosure, + Some loadClosure, checkedImplFileOpt, tcOpenDeclarations ) @@ -1593,7 +1664,7 @@ type internal TransparentCompiler Trace.TraceInformation($"Using assembly on disk: {name}") return ProjectAssemblyDataResult.Unavailable true else - match! ComputeBootstrapInfo projectSnapshot with + match! ComputeBootstrapInfo (Some fileName) projectSnapshot with | None, _ -> Trace.TraceInformation($"Using assembly on disk (unintentionally): {name}") return ProjectAssemblyDataResult.Unavailable true @@ -1618,7 +1689,7 @@ type internal TransparentCompiler projectSnapshot.FullKey, node { - match! ComputeBootstrapInfo projectSnapshot with + match! ComputeBootstrapInfo None projectSnapshot with | None, creationDiags -> return FSharpCheckProjectResults(projectSnapshot.ProjectFileName, None, keepAssemblyContents, creationDiags, None) | Some bootstrapInfo, creationDiags -> @@ -1689,7 +1760,7 @@ type internal TransparentCompiler let tryGetSink (fileName: string) (projectSnapshot: ProjectSnapshot) = node { - match! ComputeBootstrapInfo projectSnapshot with + match! ComputeBootstrapInfo (Some fileName) projectSnapshot with | None, _ -> return None | Some bootstrapInfo, _creationDiags -> From 858d1e65fc667454d9baffb5c0f5e151a085403e Mon Sep 17 00:00:00 2001 From: dawe Date: Fri, 2 Feb 2024 15:55:42 +0100 Subject: [PATCH 05/11] don't default to true for assumeDotNetFramework --- src/Compiler/Service/TransparentCompiler.fs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Compiler/Service/TransparentCompiler.fs b/src/Compiler/Service/TransparentCompiler.fs index 843ea11c0fa..757fe00ee2f 100644 --- a/src/Compiler/Service/TransparentCompiler.fs +++ b/src/Compiler/Service/TransparentCompiler.fs @@ -386,8 +386,7 @@ type internal TransparentCompiler let useFsiAuxLib = defaultArg useFsiAuxLib true let useSdkRefs = defaultArg useSdkRefs true let reduceMemoryUsage = ReduceMemoryFlag.Yes - // Do we assume .NET Framework references for scripts? - let assumeDotNetFramework = defaultArg assumeDotNetFramework true + let assumeDotNetFramework = defaultArg assumeDotNetFramework false let applyCompilerOptions tcConfig = let fsiCompilerOptions = GetCoreFsiCompilerOptions tcConfig From 69323a40a94c486e2959d396a29d8694ddcdb5fc Mon Sep 17 00:00:00 2001 From: dawe Date: Fri, 2 Feb 2024 19:10:09 +0100 Subject: [PATCH 06/11] take another approach using the assumption that UseScriptResolutionRules and a single source file means we are doing a script --- src/Compiler/Service/TransparentCompiler.fs | 267 ++++++++++---------- 1 file changed, 135 insertions(+), 132 deletions(-) diff --git a/src/Compiler/Service/TransparentCompiler.fs b/src/Compiler/Service/TransparentCompiler.fs index 757fe00ee2f..5bfafb1c7da 100644 --- a/src/Compiler/Service/TransparentCompiler.fs +++ b/src/Compiler/Service/TransparentCompiler.fs @@ -590,89 +590,113 @@ type internal TransparentCompiler } ] - let ComputeTcConfigBuilder (projectSnapshot: ProjectSnapshotBase<_>) = - - let useSimpleResolutionSwitch = "--simpleresolution" - let commandLineArgs = projectSnapshot.CommandLineOptions - let defaultFSharpBinariesDir = FSharpCheckerResultsSettings.defaultFSharpBinariesDir - let useScriptResolutionRules = projectSnapshot.UseScriptResolutionRules - - let projectReferences = - getProjectReferences projectSnapshot "ComputeTcConfigBuilder" - - let loadClosureOpt: LoadClosure option = None // ToDo - - let getSwitchValue (switchString: string) = - match commandLineArgs |> List.tryFindIndex (fun s -> s.StartsWithOrdinal switchString) with - | Some idx -> Some(commandLineArgs[idx].Substring(switchString.Length)) - | _ -> None - - let sdkDirOverride = - match loadClosureOpt with - | None -> None - | Some loadClosure -> loadClosure.SdkDirOverride - - // see also fsc.fs: runFromCommandLineToImportingAssemblies(), as there are many similarities to where the PS creates a tcConfigB - let tcConfigB = - TcConfigBuilder.CreateNew( - legacyReferenceResolver, - defaultFSharpBinariesDir, - implicitIncludeDir = projectSnapshot.ProjectDirectory, - reduceMemoryUsage = ReduceMemoryFlag.Yes, - isInteractive = useScriptResolutionRules, - isInvalidationSupported = true, - defaultCopyFSharpCore = CopyFSharpCoreFlag.No, - tryGetMetadataSnapshot = tryGetMetadataSnapshot, - sdkDirOverride = sdkDirOverride, - rangeForErrors = range0 - ) + let ComputeTcConfigBuilder (projectSnapshot: ProjectSnapshot) = + node { + let useSimpleResolutionSwitch = "--simpleresolution" + let commandLineArgs = projectSnapshot.CommandLineOptions + let defaultFSharpBinariesDir = FSharpCheckerResultsSettings.defaultFSharpBinariesDir + let useScriptResolutionRules = projectSnapshot.UseScriptResolutionRules - tcConfigB.primaryAssembly <- - match loadClosureOpt with - | None -> PrimaryAssembly.Mscorlib - | Some loadClosure -> - if loadClosure.UseDesktopFramework then - PrimaryAssembly.Mscorlib - else - PrimaryAssembly.System_Runtime + let projectReferences = + getProjectReferences projectSnapshot "ComputeTcConfigBuilder" - tcConfigB.resolutionEnvironment <- (LegacyResolutionEnvironment.EditingOrCompilation true) + let getSwitchValue (switchString: string) = + match commandLineArgs |> List.tryFindIndex (fun s -> s.StartsWithOrdinal switchString) with + | Some idx -> Some(commandLineArgs[idx].Substring(switchString.Length)) + | _ -> None - tcConfigB.conditionalDefines <- - let define = - if useScriptResolutionRules then - "INTERACTIVE" - else - "COMPILED" + let useSimpleResolution = + (getSwitchValue useSimpleResolutionSwitch) |> Option.isSome - define :: tcConfigB.conditionalDefines + let! (loadClosureOpt: LoadClosure option) = + match projectSnapshot.SourceFiles, projectSnapshot.UseScriptResolutionRules with + | [ fsxFile ], true -> // assuming UseScriptResolutionRules and a single source file means we are doing this for a script + node { + let! source = fsxFile.GetSource() |> NodeCode.AwaitTask - tcConfigB.projectReferences <- projectReferences + let! closure = + ComputeScriptClosure + fsxFile.FileName + source + defaultFSharpBinariesDir + useSimpleResolution + None + None + None + None + projectSnapshot - tcConfigB.useSimpleResolution <- (getSwitchValue useSimpleResolutionSwitch) |> Option.isSome + return (Some closure) + } + | _ -> node { return None } - // Apply command-line arguments and collect more source files if they are in the arguments - let sourceFilesNew = - ApplyCommandLineArgs(tcConfigB, projectSnapshot.SourceFileNames, commandLineArgs) + let sdkDirOverride = + match loadClosureOpt with + | None -> None + | Some loadClosure -> loadClosure.SdkDirOverride + + // see also fsc.fs: runFromCommandLineToImportingAssemblies(), as there are many similarities to where the PS creates a tcConfigB + let tcConfigB = + TcConfigBuilder.CreateNew( + legacyReferenceResolver, + defaultFSharpBinariesDir, + implicitIncludeDir = projectSnapshot.ProjectDirectory, + reduceMemoryUsage = ReduceMemoryFlag.Yes, + isInteractive = useScriptResolutionRules, + isInvalidationSupported = true, + defaultCopyFSharpCore = CopyFSharpCoreFlag.No, + tryGetMetadataSnapshot = tryGetMetadataSnapshot, + sdkDirOverride = sdkDirOverride, + rangeForErrors = range0 + ) - // Never open PDB files for the language service, even if --standalone is specified - tcConfigB.openDebugInformationForLaterStaticLinking <- false + tcConfigB.primaryAssembly <- + match loadClosureOpt with + | None -> PrimaryAssembly.Mscorlib + | Some loadClosure -> + if loadClosure.UseDesktopFramework then + PrimaryAssembly.Mscorlib + else + PrimaryAssembly.System_Runtime - tcConfigB.xmlDocInfoLoader <- - { new IXmlDocumentationInfoLoader with - /// Try to load xml documentation associated with an assembly by the same file path with the extension ".xml". - member _.TryLoad(assemblyFileName) = - let xmlFileName = Path.ChangeExtension(assemblyFileName, ".xml") + tcConfigB.resolutionEnvironment <- (LegacyResolutionEnvironment.EditingOrCompilation true) - // REVIEW: File IO - Will eventually need to change this to use a file system interface of some sort. - XmlDocumentationInfo.TryCreateFromFile(xmlFileName) - } - |> Some + tcConfigB.conditionalDefines <- + let define = + if useScriptResolutionRules then + "INTERACTIVE" + else + "COMPILED" + + define :: tcConfigB.conditionalDefines - tcConfigB.parallelReferenceResolution <- parallelReferenceResolution - tcConfigB.captureIdentifiersWhenParsing <- captureIdentifiersWhenParsing + tcConfigB.projectReferences <- projectReferences - tcConfigB, sourceFilesNew, loadClosureOpt + tcConfigB.useSimpleResolution <- useSimpleResolution + + // Apply command-line arguments and collect more source files if they are in the arguments + let sourceFilesNew = + ApplyCommandLineArgs(tcConfigB, projectSnapshot.SourceFileNames, commandLineArgs) + + // Never open PDB files for the language service, even if --standalone is specified + tcConfigB.openDebugInformationForLaterStaticLinking <- false + + tcConfigB.xmlDocInfoLoader <- + { new IXmlDocumentationInfoLoader with + /// Try to load xml documentation associated with an assembly by the same file path with the extension ".xml". + member _.TryLoad(assemblyFileName) = + let xmlFileName = Path.ChangeExtension(assemblyFileName, ".xml") + + // REVIEW: File IO - Will eventually need to change this to use a file system interface of some sort. + XmlDocumentationInfo.TryCreateFromFile(xmlFileName) + } + |> Some + + tcConfigB.parallelReferenceResolution <- parallelReferenceResolution + tcConfigB.captureIdentifiersWhenParsing <- captureIdentifiersWhenParsing + + return tcConfigB, sourceFilesNew, loadClosureOpt + } let mutable BootstrapInfoIdCounter = 0 @@ -756,68 +780,47 @@ type internal TransparentCompiler } ) - let computeBootstrapInfoInner (fileName: string option) (projectSnapshot: ProjectSnapshot) = + let computeBootstrapInfoInner (projectSnapshot: ProjectSnapshot) = node { - let tcConfigB, sourceFiles, _ = ComputeTcConfigBuilder projectSnapshot + let! tcConfigB, sourceFiles, loadClosureOpt = ComputeTcConfigBuilder projectSnapshot // If this is a builder for a script, re-apply the settings inferred from the // script and its load closure to the configuration. // // NOTE: it would probably be cleaner and more accurate to re-run the load closure at this point. let setupConfigFromLoadClosure () = - match fileName, projectSnapshot.UseScriptResolutionRules with - | Some fileName, true -> - node { - let sourceFile = - projectSnapshot.SourceFiles |> List.find (fun x -> x.FileName = fileName) - - let! source = sourceFile.GetSource() |> NodeCode.AwaitTask - - let! loadClosure = - ComputeScriptClosure - fileName - source - tcConfigB.defaultFSharpBinariesDir - tcConfigB.useSimpleResolution - (Some tcConfigB.useFsiAuxLib) - (Some tcConfigB.useSdkRefs) - tcConfigB.sdkDirOverride - None - projectSnapshot - - let dllReferences = - [ - for reference in tcConfigB.referencedDLLs do - // If there's (one or more) resolutions of closure references then yield them all - match - loadClosure.References - |> List.tryFind (fun (resolved, _) -> resolved = reference.Text) - with - | Some(resolved, closureReferences) -> - for closureReference in closureReferences do - yield AssemblyReference(closureReference.originalReference.Range, resolved, None) - | None -> yield reference - ] - - tcConfigB.referencedDLLs <- [] - - tcConfigB.primaryAssembly <- - (if loadClosure.UseDesktopFramework then - PrimaryAssembly.Mscorlib - else - PrimaryAssembly.System_Runtime) - // Add one by one to remove duplicates - dllReferences - |> List.iter (fun dllReference -> tcConfigB.AddReferencedAssemblyByPath(dllReference.Range, dllReference.Text)) - - tcConfigB.knownUnresolvedReferences <- loadClosure.UnresolvedReferences - - return Some loadClosure - } - | _ -> node { return None } - - let! loadClosureOpt = setupConfigFromLoadClosure () + match loadClosureOpt with + | Some loadClosure -> + let dllReferences = + [ + for reference in tcConfigB.referencedDLLs do + // If there's (one or more) resolutions of closure references then yield them all + match + loadClosure.References + |> List.tryFind (fun (resolved, _) -> resolved = reference.Text) + with + | Some(resolved, closureReferences) -> + for closureReference in closureReferences do + yield AssemblyReference(closureReference.originalReference.Range, resolved, None) + | None -> yield reference + ] + + tcConfigB.referencedDLLs <- [] + + tcConfigB.primaryAssembly <- + (if loadClosure.UseDesktopFramework then + PrimaryAssembly.Mscorlib + else + PrimaryAssembly.System_Runtime) + // Add one by one to remove duplicates + dllReferences + |> List.iter (fun dllReference -> tcConfigB.AddReferencedAssemblyByPath(dllReference.Range, dllReference.Text)) + + tcConfigB.knownUnresolvedReferences <- loadClosure.UnresolvedReferences + | None -> () + + setupConfigFromLoadClosure () let tcConfig = TcConfig.Create(tcConfigB, validate = true) let outFile, _, assemblyName = tcConfigB.DecideNames sourceFiles @@ -847,7 +850,7 @@ type internal TransparentCompiler } } - let ComputeBootstrapInfo (fileName: string option) (projectSnapshot: ProjectSnapshot) = + let ComputeBootstrapInfo (projectSnapshot: ProjectSnapshot) = caches.BootstrapInfo.Get( projectSnapshot.NoFileVersionsKey, @@ -862,7 +865,7 @@ type internal TransparentCompiler let! bootstrapInfoOpt = node { try - return! computeBootstrapInfoInner fileName projectSnapshot + return! computeBootstrapInfoInner projectSnapshot with exn -> errorRecoveryNoRange exn return None @@ -1413,7 +1416,7 @@ type internal TransparentCompiler use _ = Activity.start "ComputeParseAndCheckFileInProject" [| Activity.Tags.fileName, fileName |> Path.GetFileName |] - match! ComputeBootstrapInfo (Some fileName) projectSnapshot with + match! ComputeBootstrapInfo projectSnapshot with | None, creationDiags -> return emptyParseResult fileName creationDiags, FSharpCheckFileAnswer.Aborted | Some bootstrapInfo, creationDiags -> @@ -1663,7 +1666,7 @@ type internal TransparentCompiler Trace.TraceInformation($"Using assembly on disk: {name}") return ProjectAssemblyDataResult.Unavailable true else - match! ComputeBootstrapInfo (Some fileName) projectSnapshot with + match! ComputeBootstrapInfo projectSnapshot with | None, _ -> Trace.TraceInformation($"Using assembly on disk (unintentionally): {name}") return ProjectAssemblyDataResult.Unavailable true @@ -1688,7 +1691,7 @@ type internal TransparentCompiler projectSnapshot.FullKey, node { - match! ComputeBootstrapInfo None projectSnapshot with + match! ComputeBootstrapInfo projectSnapshot with | None, creationDiags -> return FSharpCheckProjectResults(projectSnapshot.ProjectFileName, None, keepAssemblyContents, creationDiags, None) | Some bootstrapInfo, creationDiags -> @@ -1759,7 +1762,7 @@ type internal TransparentCompiler let tryGetSink (fileName: string) (projectSnapshot: ProjectSnapshot) = node { - match! ComputeBootstrapInfo (Some fileName) projectSnapshot with + match! ComputeBootstrapInfo projectSnapshot with | None, _ -> return None | Some bootstrapInfo, _creationDiags -> @@ -1843,7 +1846,7 @@ type internal TransparentCompiler // Activity.start "ParseFile" [| Activity.Tags.fileName, fileName |> Path.GetFileName |] // TODO: might need to deal with exceptions here: - let tcConfigB, sourceFileNames, _ = ComputeTcConfigBuilder projectSnapshot + let! tcConfigB, sourceFileNames, _ = ComputeTcConfigBuilder projectSnapshot let tcConfig = TcConfig.Create(tcConfigB, validate = true) From 513fe62dae0cf95cf6b1613da152a2123c5aaa10 Mon Sep 17 00:00:00 2001 From: dawe Date: Mon, 5 Feb 2024 15:23:54 +0100 Subject: [PATCH 07/11] add ScriptClosure to CompilerCaches members in fsi --- src/Compiler/Service/TransparentCompiler.fsi | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Compiler/Service/TransparentCompiler.fsi b/src/Compiler/Service/TransparentCompiler.fsi index 00167ccc67f..14562f34f15 100644 --- a/src/Compiler/Service/TransparentCompiler.fsi +++ b/src/Compiler/Service/TransparentCompiler.fsi @@ -132,6 +132,8 @@ type internal CompilerCaches = member TcIntermediate: AsyncMemoize<(string * (string * string)), (string * int), TcIntermediate> + member ScriptClosure: AsyncMemoize<(string * (string * string)), string, LoadClosure> + member TcLastFile: AsyncMemoizeDisabled type internal TransparentCompiler = From 907e6e71ee6139542be181b2c059ffcf33c17896 Mon Sep 17 00:00:00 2001 From: dawe Date: Mon, 5 Feb 2024 15:24:39 +0100 Subject: [PATCH 08/11] Add some basic script support to SyntheticProject --- tests/FSharp.Test.Utilities/ProjectGeneration.fs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/tests/FSharp.Test.Utilities/ProjectGeneration.fs b/tests/FSharp.Test.Utilities/ProjectGeneration.fs index 144e535bccb..2236eee988d 100644 --- a/tests/FSharp.Test.Utilities/ProjectGeneration.fs +++ b/tests/FSharp.Test.Utilities/ProjectGeneration.fs @@ -240,7 +240,8 @@ type SyntheticProject = NugetReferences: Reference list FrameworkReferences: Reference list /// If set to true this project won't cause an exception if there are errors in the initial check - SkipInitialCheck: bool } + SkipInitialCheck: bool + UseScriptResolutionRules: bool } static member Create(?name: string) = let name = defaultArg name $"TestProject_{Guid.NewGuid().ToString()[..7]}" @@ -255,13 +256,17 @@ type SyntheticProject = AutoAddModules = true NugetReferences = [] FrameworkReferences = [] - SkipInitialCheck = false } + SkipInitialCheck = false + UseScriptResolutionRules = false } static member Create([] sourceFiles: SyntheticSourceFile[]) = { SyntheticProject.Create() with SourceFiles = sourceFiles |> List.ofArray } static member Create(name: string, [] sourceFiles: SyntheticSourceFile[]) = { SyntheticProject.Create(name) with SourceFiles = sourceFiles |> List.ofArray } + + static member CreateForScript(scriptFile: SyntheticSourceFile) = + { SyntheticProject.Create() with SourceFiles = [scriptFile]; UseScriptResolutionRules = true } member this.Find fileId = this.SourceFiles @@ -339,7 +344,7 @@ type SyntheticProject = [| for p in this.DependsOn do FSharpReferencedProject.FSharpReference(p.OutputFilename, p.GetProjectOptions checker) |] IsIncompleteTypeCheckEnvironment = false - UseScriptResolutionRules = false + UseScriptResolutionRules = this.UseScriptResolutionRules LoadTime = DateTime() UnresolvedReferences = None OriginalLoadReferences = [] From 6059bbe85151dd51c1215c19047045578acc64dd Mon Sep 17 00:00:00 2001 From: dawe Date: Mon, 5 Feb 2024 15:25:07 +0100 Subject: [PATCH 09/11] Add tests for LoadClosure caching --- .../FSharpChecker/TransparentCompiler.fs | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/tests/FSharp.Compiler.ComponentTests/FSharpChecker/TransparentCompiler.fs b/tests/FSharp.Compiler.ComponentTests/FSharpChecker/TransparentCompiler.fs index 5ff288185c4..664b79d4e0e 100644 --- a/tests/FSharp.Compiler.ComponentTests/FSharpChecker/TransparentCompiler.fs +++ b/tests/FSharp.Compiler.ComponentTests/FSharpChecker/TransparentCompiler.fs @@ -842,3 +842,57 @@ let ``TypeCheck last file in project with transparent compiler`` useTransparentC clearCache checkFile lastFile expectOk } + +[] +let ``LoadClosure for script is computed once`` () = + let project = SyntheticProject.CreateForScript( + sourceFile "First" []) + + let cacheEvents = ConcurrentQueue() + + ProjectWorkflowBuilder(project, useTransparentCompiler = true) { + withChecker (fun checker -> + async { + do! Async.Sleep 50 // wait for events from initial project check + checker.Caches.ScriptClosure.OnEvent cacheEvents.Enqueue + }) + + checkFile "First" expectOk + } |> ignore + + let closureComputations = + cacheEvents + |> Seq.groupBy (fun (_e, (_l, (f, _p), _)) -> Path.GetFileName f) + |> Seq.map (fun (k, g) -> k, g |> Seq.map fst |> Seq.toList) + |> Map + + Assert.Empty(closureComputations) + +[] +let ``LoadClosure for script is recomputed after changes`` () = + let project = SyntheticProject.CreateForScript( + sourceFile "First" []) + + let cacheEvents = ConcurrentQueue() + + ProjectWorkflowBuilder(project, useTransparentCompiler = true) { + withChecker (fun checker -> + async { + do! Async.Sleep 50 // wait for events from initial project check + checker.Caches.ScriptClosure.OnEvent cacheEvents.Enqueue + }) + + checkFile "First" expectOk + updateFile "First" updateInternal + checkFile "First" expectOk + updateFile "First" updatePublicSurface + checkFile "First" expectOk + } |> ignore + + let closureComputations = + cacheEvents + |> Seq.groupBy (fun (_e, (_l, (f, _p), _)) -> Path.GetFileName f) + |> Seq.map (fun (k, g) -> k, g |> Seq.map fst |> Seq.toList) + |> Map + + Assert.Equal([Weakened; Requested; Started; Finished; Weakened; Requested; Started; Finished], closureComputations["FileFirst.fs"]) \ No newline at end of file From edbede80df0e9c3036c9a996dec9fde57af6630e Mon Sep 17 00:00:00 2001 From: dawe Date: Tue, 6 Feb 2024 13:17:14 +0100 Subject: [PATCH 10/11] bring in changes from b47ba47cc1eafd96dbedf3e3934331d785e049d2 --- tests/FSharp.Test.Utilities/CompilerAssert.fs | 2 +- tests/FSharp.Test.Utilities/ProjectGeneration.fs | 2 +- tests/service/Common.fs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/FSharp.Test.Utilities/CompilerAssert.fs b/tests/FSharp.Test.Utilities/CompilerAssert.fs index 7951ba31d5d..8117e5390ce 100644 --- a/tests/FSharp.Test.Utilities/CompilerAssert.fs +++ b/tests/FSharp.Test.Utilities/CompilerAssert.fs @@ -259,7 +259,7 @@ and Compilation = module rec CompilerAssertHelpers = - let useTransparentCompiler = FSharp.Compiler.CompilerConfig.FSharpExperimentalFeaturesEnabledAutomatically + let useTransparentCompiler = true let checker = FSharpChecker.Create(suggestNamesForErrors=true, useTransparentCompiler=useTransparentCompiler) // Unlike C# whose entrypoint is always string[] F# can make an entrypoint with 0 args, or with an array of string[] diff --git a/tests/FSharp.Test.Utilities/ProjectGeneration.fs b/tests/FSharp.Test.Utilities/ProjectGeneration.fs index 2236eee988d..74a4eab5b01 100644 --- a/tests/FSharp.Test.Utilities/ProjectGeneration.fs +++ b/tests/FSharp.Test.Utilities/ProjectGeneration.fs @@ -931,7 +931,7 @@ type ProjectWorkflowBuilder ?isExistingProject ) = - let useTransparentCompiler = defaultArg useTransparentCompiler FSharp.Compiler.CompilerConfig.FSharpExperimentalFeaturesEnabledAutomatically + let useTransparentCompiler = defaultArg useTransparentCompiler true let useGetSource = not useTransparentCompiler && defaultArg useGetSource false let useChangeNotifications = not useTransparentCompiler && defaultArg useChangeNotifications false let autoStart = defaultArg autoStart true diff --git a/tests/service/Common.fs b/tests/service/Common.fs index 8516948626a..11723a53a8a 100644 --- a/tests/service/Common.fs +++ b/tests/service/Common.fs @@ -31,7 +31,7 @@ type Async with task.Result // Create one global interactive checker instance -let checker = FSharpChecker.Create(useTransparentCompiler=FSharp.Compiler.CompilerConfig.FSharpExperimentalFeaturesEnabledAutomatically) +let checker = FSharpChecker.Create(useTransparentCompiler=true) type TempFile(ext, contents: string) = let tmpFile = Path.ChangeExtension(tryCreateTemporaryFileName (), ext) From e69744caf42b35ae6ac16c48b4059c411ca7d812 Mon Sep 17 00:00:00 2001 From: dawe Date: Tue, 6 Feb 2024 16:13:10 +0100 Subject: [PATCH 11/11] Revert "bring in changes from b47ba47cc1eafd96dbedf3e3934331d785e049d2" This reverts commit edbede80df0e9c3036c9a996dec9fde57af6630e. --- tests/FSharp.Test.Utilities/CompilerAssert.fs | 2 +- tests/FSharp.Test.Utilities/ProjectGeneration.fs | 2 +- tests/service/Common.fs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/FSharp.Test.Utilities/CompilerAssert.fs b/tests/FSharp.Test.Utilities/CompilerAssert.fs index 8117e5390ce..7951ba31d5d 100644 --- a/tests/FSharp.Test.Utilities/CompilerAssert.fs +++ b/tests/FSharp.Test.Utilities/CompilerAssert.fs @@ -259,7 +259,7 @@ and Compilation = module rec CompilerAssertHelpers = - let useTransparentCompiler = true + let useTransparentCompiler = FSharp.Compiler.CompilerConfig.FSharpExperimentalFeaturesEnabledAutomatically let checker = FSharpChecker.Create(suggestNamesForErrors=true, useTransparentCompiler=useTransparentCompiler) // Unlike C# whose entrypoint is always string[] F# can make an entrypoint with 0 args, or with an array of string[] diff --git a/tests/FSharp.Test.Utilities/ProjectGeneration.fs b/tests/FSharp.Test.Utilities/ProjectGeneration.fs index 74a4eab5b01..2236eee988d 100644 --- a/tests/FSharp.Test.Utilities/ProjectGeneration.fs +++ b/tests/FSharp.Test.Utilities/ProjectGeneration.fs @@ -931,7 +931,7 @@ type ProjectWorkflowBuilder ?isExistingProject ) = - let useTransparentCompiler = defaultArg useTransparentCompiler true + let useTransparentCompiler = defaultArg useTransparentCompiler FSharp.Compiler.CompilerConfig.FSharpExperimentalFeaturesEnabledAutomatically let useGetSource = not useTransparentCompiler && defaultArg useGetSource false let useChangeNotifications = not useTransparentCompiler && defaultArg useChangeNotifications false let autoStart = defaultArg autoStart true diff --git a/tests/service/Common.fs b/tests/service/Common.fs index 11723a53a8a..8516948626a 100644 --- a/tests/service/Common.fs +++ b/tests/service/Common.fs @@ -31,7 +31,7 @@ type Async with task.Result // Create one global interactive checker instance -let checker = FSharpChecker.Create(useTransparentCompiler=true) +let checker = FSharpChecker.Create(useTransparentCompiler=FSharp.Compiler.CompilerConfig.FSharpExperimentalFeaturesEnabledAutomatically) type TempFile(ext, contents: string) = let tmpFile = Path.ChangeExtension(tryCreateTemporaryFileName (), ext)