Skip to content
This repository was archived by the owner on Dec 23, 2024. It is now read-only.

Commit 34f08b3

Browse files
authored
Enable in-memory cross project referencing for C# -> F# projects (dotnet#11301)
* Added FSharpReferencedProject * Touching up the API * Refactoring and using a Stream instead of a byte[] * Removing a few API endpoints * Fixing build * Updating surface area * Added ConditionalWeakTable for compilation emission * Just in case, put a try/with around creating a pe reference * Remove try/with * Adding C# in-memory reference for compiler service tests
1 parent 63d8ffe commit 34f08b3

File tree

1 file changed

+36
-2
lines changed

1 file changed

+36
-2
lines changed

LanguageService/FSharpProjectOptionsManager.fs

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ open Microsoft.VisualStudio.Shell.Interop
2222
open Microsoft.VisualStudio.LanguageServices.Implementation.TaskList
2323
open Microsoft.CodeAnalysis.ExternalAccess.FSharp.LanguageServices
2424
open Microsoft.VisualStudio.FSharp.Interactive.Session
25+
open System.Runtime.CompilerServices
2526

2627
[<AutoOpen>]
2728
module private FSharpProjectOptionsHelpers =
@@ -65,7 +66,16 @@ module private FSharpProjectOptionsHelpers =
6566
let doesProjectIdDiffer = p1.ProjectId <> p2.ProjectId
6667
let p1 = oldProject.Solution.GetProject(p1.ProjectId)
6768
let p2 = newProject.Solution.GetProject(p2.ProjectId)
68-
doesProjectIdDiffer || p1.Version <> p2.Version
69+
doesProjectIdDiffer ||
70+
(
71+
// For F# projects, just check the version until we have a better in-memory model for them.
72+
if p1.Language = LanguageNames.FSharp then
73+
p1.Version <> p2.Version
74+
else
75+
let v1 = p1.GetDependentVersionAsync(ct).Result
76+
let v2 = p2.GetDependentVersionAsync(ct).Result
77+
v1 <> v2
78+
)
6979
)
7080

7181
let isProjectInvalidated (oldProject: Project) (newProject: Project) (settings: EditorOptions) ct =
@@ -94,6 +104,26 @@ type private FSharpProjectOptionsReactor (workspace: Workspace, settings: Editor
94104
let cache = ConcurrentDictionary<ProjectId, Project * FSharpParsingOptions * FSharpProjectOptions>()
95105
let singleFileCache = ConcurrentDictionary<DocumentId, VersionStamp * FSharpParsingOptions * FSharpProjectOptions>()
96106

107+
// This is used to not constantly emit the same compilation.
108+
let weakPEReferences = ConditionalWeakTable<Compilation, FSharpReferencedProject>()
109+
110+
let createPEReference (referencedProject: Project) (comp: Compilation) ct =
111+
match weakPEReferences.TryGetValue comp with
112+
| true, fsRefProj -> fsRefProj
113+
| _ ->
114+
let fsRefProj =
115+
FSharpReferencedProject.CreatePortableExecutable(
116+
referencedProject.OutputFilePath,
117+
DateTime.UtcNow,
118+
let ms = new MemoryStream() // do not dispose the stream as it will be owned on the reference.
119+
let emitOptions = Emit.EmitOptions(metadataOnly = true, includePrivateMembers = false, tolerateErrors = true)
120+
comp.Emit(ms, options = emitOptions, cancellationToken = ct) |> ignore
121+
ms.Position <- 0L
122+
ms
123+
)
124+
weakPEReferences.Add(comp, fsRefProj)
125+
fsRefProj
126+
97127
let rec tryComputeOptionsByFile (document: Document) (ct: CancellationToken) userOpName =
98128
async {
99129
let! fileStamp = document.GetTextVersionAsync(ct) |> Async.AwaitTask
@@ -170,7 +200,11 @@ type private FSharpProjectOptionsReactor (workspace: Workspace, settings: Editor
170200
if referencedProject.Language = FSharpConstants.FSharpLanguageName then
171201
match! tryComputeOptions referencedProject ct with
172202
| None -> canBail <- true
173-
| Some(_, projectOptions) -> referencedProjects.Add(referencedProject.OutputFilePath, projectOptions)
203+
| Some(_, projectOptions) -> referencedProjects.Add(FSharpReferencedProject.CreateFSharp(referencedProject.OutputFilePath, projectOptions))
204+
else
205+
let! comp = referencedProject.GetCompilationAsync(ct) |> Async.AwaitTask
206+
let peRef = createPEReference referencedProject comp ct
207+
referencedProjects.Add(peRef)
174208

175209
if canBail then
176210
return None

0 commit comments

Comments
 (0)