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 27f2c0070d7..37488f8c3fa 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md +++ b/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md @@ -180,6 +180,7 @@ * IL: hold custom attributes in fields rather than a union case ([PR #20287](https://github.com/dotnet/fsharp/pull/20287)) * IL: reuse the cached ILTypeRef in ILTypeInfo.FromType ([PR #20255](https://github.com/dotnet/fsharp/pull/20255)) * Name resolution: group C#-style extension members per `open` and extended type ([PR #20298](https://github.com/dotnet/fsharp/pull/20298)) +* `FSharpProjectSnapshot.FromOptions` takes an optional `getReferenceStamp: string -> DateTime`, so a host that already tracks the last-write times of on-disk references can supply them instead of having every `-r:` stat'd again each time a snapshot is built. ([PR #20459](https://github.com/dotnet/fsharp/pull/20459)) ### Improved diff --git a/src/Compiler/Service/FSharpProjectSnapshot.fs b/src/Compiler/Service/FSharpProjectSnapshot.fs index 02c368e7f46..19b61c1c423 100644 --- a/src/Compiler/Service/FSharpProjectSnapshot.fs +++ b/src/Compiler/Service/FSharpProjectSnapshot.fs @@ -647,8 +647,11 @@ and [] FSha ProjectSnapshotBase(projectConfig, referencedProjects, sourceFiles) |> FSharpProjectSnapshot - static member FromOptions(options: FSharpProjectOptions, getFileSnapshot, ?snapshotAccumulator) = + static member FromOptions + (options: FSharpProjectOptions, getFileSnapshot, ?snapshotAccumulator, ?getReferenceStamp: string -> DateTime) + = let snapshotAccumulator = defaultArg snapshotAccumulator (Dictionary()) + let getReferenceStamp = defaultArg getReferenceStamp FileSystem.GetLastWriteTimeShim async { @@ -665,7 +668,8 @@ and [] FSha |> Seq.map (function | FSharpReferencedProject.FSharpReference(outputName, options) -> async { - let! snapshot = FSharpProjectSnapshot.FromOptions(options, getFileSnapshot, snapshotAccumulator) + let! snapshot = + FSharpProjectSnapshot.FromOptions(options, getFileSnapshot, snapshotAccumulator, getReferenceStamp) return FSharpReferencedProjectSnapshot.FSharpReference(outputName, snapshot) } @@ -686,7 +690,7 @@ and [] FSha { Path = path - LastModified = FileSystem.GetLastWriteTimeShim(path) + LastModified = getReferenceStamp path }) ) diff --git a/tests/FSharp.Compiler.ComponentTests/FSharpChecker/ProjectSnapshot.fs b/tests/FSharp.Compiler.ComponentTests/FSharpChecker/ProjectSnapshot.fs index 90e28cc63ac..900b8aa04b0 100644 --- a/tests/FSharp.Compiler.ComponentTests/FSharpChecker/ProjectSnapshot.fs +++ b/tests/FSharp.Compiler.ComponentTests/FSharpChecker/ProjectSnapshot.fs @@ -1,104 +1,61 @@ module FSharpChecker.ProjectSnapshot -open Xunit open System +open System.IO +open System.Threading.Tasks +open FSharp.Compiler.CodeAnalysis open FSharp.Compiler.CodeAnalysis.ProjectSnapshot +open Xunit - -// TODO: restore tests - -//[] -//let WithoutImplFilesThatHaveSignatures () = - -// let snapshot = FSharpProjectSnapshot.Create( -// projectFileName = "Dummy.fsproj", -// projectId = None, -// sourceFiles = [ -// { FileName = "A.fsi"; Version = "1"; GetSource = Unchecked.defaultof<_> } -// { FileName = "A.fs"; Version = "1"; GetSource = Unchecked.defaultof<_> } -// { FileName = "B.fs"; Version = "1"; GetSource = Unchecked.defaultof<_> } -// { FileName = "C.fsi"; Version = "1"; GetSource = Unchecked.defaultof<_> } -// { FileName = "C.fs"; Version = "1"; GetSource = Unchecked.defaultof<_> } -// ], -// referencesOnDisk = [], -// otherOptions = [], -// referencedProjects = [], -// isIncompleteTypeCheckEnvironment = true, -// useScriptResolutionRules = false, -// loadTime = DateTime(1234, 5, 6), -// unresolvedReferences = None, -// originalLoadReferences = [], -// stamp = None -// ) - -// let result = snapshot.WithoutImplFilesThatHaveSignatures - -// let expected = [| "A.fsi"; "B.fs"; "C.fsi" |] - -// Assert.Equal(expected, result.SourceFileNames |> List.toArray) - -// Assert.Equal(result.FullVersion, snapshot.SignatureVersion) - -//[] -//let WithoutImplFilesThatHaveSignaturesExceptLastOne () = - -// let snapshot = FSharpProjectSnapshot.Create( -// projectFileName = "Dummy.fsproj", -// projectId = None, -// sourceFiles = [ -// { FileName = "A.fsi"; Version = "1"; GetSource = Unchecked.defaultof<_> } -// { FileName = "A.fs"; Version = "1"; GetSource = Unchecked.defaultof<_> } -// { FileName = "B.fs"; Version = "1"; GetSource = Unchecked.defaultof<_> } -// { FileName = "C.fsi"; Version = "1"; GetSource = Unchecked.defaultof<_> } -// { FileName = "C.fs"; Version = "1"; GetSource = Unchecked.defaultof<_> } -// ], -// referencesOnDisk = [], -// otherOptions = [], -// referencedProjects = [], -// isIncompleteTypeCheckEnvironment = true, -// useScriptResolutionRules = false, -// loadTime = DateTime(1234, 5, 6), -// unresolvedReferences = None, -// originalLoadReferences = [], -// stamp = None -// ) - -// let result = snapshot.WithoutImplFilesThatHaveSignaturesExceptLastOne - -// let expected = [| "A.fsi"; "B.fs"; "C.fsi"; "C.fs" |] - -// Assert.Equal(expected, result.SourceFileNames |> List.toArray) - -// Assert.Equal(result.FullVersion, snapshot.LastFileVersion) - - -//[] -//let WithoutImplFilesThatHaveSignaturesExceptLastOne_2 () = - -// let snapshot = FSharpProjectSnapshot.Create( -// projectFileName = "Dummy.fsproj", -// projectId = None, -// sourceFiles = [ -// { FileName = "A.fs"; Version = "1"; GetSource = Unchecked.defaultof<_> } -// { FileName = "B.fs"; Version = "1"; GetSource = Unchecked.defaultof<_> } -// { FileName = "C.fs"; Version = "1"; GetSource = Unchecked.defaultof<_> } -// ], -// referencesOnDisk = [], -// otherOptions = [], -// referencedProjects = [], -// isIncompleteTypeCheckEnvironment = true, -// useScriptResolutionRules = false, -// loadTime = DateTime(1234, 5, 6), -// unresolvedReferences = None, -// originalLoadReferences = [], -// stamp = None -// ) - -// let result = snapshot.WithoutImplFilesThatHaveSignaturesExceptLastOne - -// let expected = [| "A.fs"; "B.fs"; "C.fs" |] - -// Assert.Equal(expected, result.SourceFileNames |> List.toArray) - -// Assert.Equal(result.FullVersion, snapshot.LastFileVersion) - +#nowarn "57" + +let private projectOptions projectFileName references referencedProjects = + { + ProjectFileName = projectFileName + ProjectId = None + SourceFiles = [| Path.ChangeExtension(projectFileName, ".fs") |] + OtherOptions = [| for path in references -> $"-r:{path}" |] + ReferencedProjects = referencedProjects + IsIncompleteTypeCheckEnvironment = false + UseScriptResolutionRules = false + LoadTime = DateTime.UtcNow + UnresolvedReferences = None + OriginalLoadReferences = [] + Stamp = None + } + +let private emptySource _ path = + async { return FSharpFileSnapshot.CreateFromString(path, "") } + +[] +let ``FromOptions takes reference stamps from the host, including referenced projects`` () : Task = + task { + let stamps = + dict + [ + "MainRef.dll", DateTime(2026, 1, 1, 0, 0, 0, DateTimeKind.Utc) + "LibRef.dll", DateTime(2026, 2, 2, 0, 0, 0, DateTimeKind.Utc) + ] + + let lib = projectOptions "Lib.fsproj" [ "LibRef.dll" ] [||] + + let main = + projectOptions "Main.fsproj" [ "MainRef.dll" ] [| FSharpReferencedProject.FSharpReference("Lib.dll", lib) |] + + let! snapshot = FSharpProjectSnapshot.FromOptions(main, emptySource, getReferenceStamp = (fun path -> stamps[path])) + + let libSnapshot = + match snapshot.ReferencedProjects with + | [ FSharpReferencedProjectSnapshot.FSharpReference(_, lib) ] -> lib + | other -> failwith $"Expected one referenced project, got %A{other}" + + Assert.Equal( + [ { Path = "MainRef.dll"; LastModified = stamps["MainRef.dll"] } ], + snapshot.ReferencesOnDisk + ) + + Assert.Equal( + [ { Path = "LibRef.dll"; LastModified = stamps["LibRef.dll"] } ], + libSnapshot.ReferencesOnDisk + ) + } 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 5c9c346b613..4f89e1c999e 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 @@ -2502,7 +2502,7 @@ FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectSnapshot: Microsoft.FS FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectSnapshot: Microsoft.FSharp.Collections.FSharpList`1[System.Tuple`3[FSharp.Compiler.Text.Range,System.String,System.String]] OriginalLoadReferences FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectSnapshot: Microsoft.FSharp.Collections.FSharpList`1[System.Tuple`3[FSharp.Compiler.Text.Range,System.String,System.String]] get_OriginalLoadReferences() FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectSnapshot: Microsoft.FSharp.Control.FSharpAsync`1[FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectSnapshot] FromOptions(FSharp.Compiler.CodeAnalysis.FSharpProjectOptions, FSharp.Compiler.CodeAnalysis.DocumentSource) -FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectSnapshot: Microsoft.FSharp.Control.FSharpAsync`1[FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectSnapshot] FromOptions(FSharp.Compiler.CodeAnalysis.FSharpProjectOptions, Microsoft.FSharp.Core.FSharpFunc`2[FSharp.Compiler.CodeAnalysis.FSharpProjectOptions,Microsoft.FSharp.Core.FSharpFunc`2[System.String,Microsoft.FSharp.Control.FSharpAsync`1[FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpFileSnapshot]]], Microsoft.FSharp.Core.FSharpOption`1[System.Collections.Generic.Dictionary`2[FSharp.Compiler.CodeAnalysis.FSharpProjectOptions,FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectSnapshot]]) +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectSnapshot: Microsoft.FSharp.Control.FSharpAsync`1[FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectSnapshot] FromOptions(FSharp.Compiler.CodeAnalysis.FSharpProjectOptions, Microsoft.FSharp.Core.FSharpFunc`2[FSharp.Compiler.CodeAnalysis.FSharpProjectOptions,Microsoft.FSharp.Core.FSharpFunc`2[System.String,Microsoft.FSharp.Control.FSharpAsync`1[FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpFileSnapshot]]], Microsoft.FSharp.Core.FSharpOption`1[System.Collections.Generic.Dictionary`2[FSharp.Compiler.CodeAnalysis.FSharpProjectOptions,FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectSnapshot]], Microsoft.FSharp.Core.FSharpOption`1[Microsoft.FSharp.Core.FSharpFunc`2[System.String,System.DateTime]]) FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectSnapshot: Microsoft.FSharp.Control.FSharpAsync`1[FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectSnapshot] FromOptions(FSharp.Compiler.CodeAnalysis.FSharpProjectOptions, System.String, Int32, FSharp.Compiler.Text.ISourceText, FSharp.Compiler.CodeAnalysis.DocumentSource) FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectSnapshot: Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.CodeAnalysis.FSharpUnresolvedReferencesSet] UnresolvedReferences FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectSnapshot: Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.CodeAnalysis.FSharpUnresolvedReferencesSet] get_UnresolvedReferences()