diff --git a/VisualFSharp.sln b/VisualFSharp.sln index dc754bb2de4..0ee5cddaad4 100644 --- a/VisualFSharp.sln +++ b/VisualFSharp.sln @@ -1,14 +1,14 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 -VisualStudioVersion = 15.0.26403.7 +VisualStudioVersion = 15.0.26730.3 MinimumVisualStudioVersion = 10.0.40219.1 Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FSharp.Compiler.Private", "src\fsharp\FSharp.Compiler.Private\FSharp.Compiler.Private.fsproj", "{2E4D67B4-522D-4CF7-97E4-BA940F0B18F3}" EndProject Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FSharp.VS.FSI", "vsintegration\src\FSharp.VS.FSI\FSharp.VS.FSI.fsproj", "{991DCF75-C2EB-42B6-9A0D-AA1D2409D519}" -ProjectSection(ProjectDependencies) = postProject -{2E4D67B4-522D-4CF7-97E4-BA940F0B18F3} = {2E4D67B4-522D-4CF7-97E4-BA940F0B18F3} -EndProjectSection + ProjectSection(ProjectDependencies) = postProject + {2E4D67B4-522D-4CF7-97E4-BA940F0B18F3} = {2E4D67B4-522D-4CF7-97E4-BA940F0B18F3} + EndProjectSection EndProject Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FSharp.Compiler.Server.Shared", "src\fsharp\FSharp.Compiler.Server.Shared\FSharp.Compiler.Server.Shared.fsproj", "{D5870CF0-ED51-4CBC-B3D7-6F56DA84AC06}" EndProject @@ -142,6 +142,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "FSharp.Compiler.Service.tes EndProject Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "TestTP", "tests\service\data\TestTP\TestTP.fsproj", "{FF76BD3C-5E0A-4752-B6C3-044F6E15719B}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ResourceFile", "vsintegration\ItemTemplates\ResourceFile\ResourceFile.csproj", "{0385564F-07B4-4264-AB8A-17C393E9140C}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -574,6 +576,14 @@ Global {FF76BD3C-5E0A-4752-B6C3-044F6E15719B}.Release|Any CPU.Build.0 = Release|Any CPU {FF76BD3C-5E0A-4752-B6C3-044F6E15719B}.Release|x86.ActiveCfg = Release|Any CPU {FF76BD3C-5E0A-4752-B6C3-044F6E15719B}.Release|x86.Build.0 = Release|Any CPU + {0385564F-07B4-4264-AB8A-17C393E9140C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0385564F-07B4-4264-AB8A-17C393E9140C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0385564F-07B4-4264-AB8A-17C393E9140C}.Debug|x86.ActiveCfg = Debug|Any CPU + {0385564F-07B4-4264-AB8A-17C393E9140C}.Debug|x86.Build.0 = Debug|Any CPU + {0385564F-07B4-4264-AB8A-17C393E9140C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0385564F-07B4-4264-AB8A-17C393E9140C}.Release|Any CPU.Build.0 = Release|Any CPU + {0385564F-07B4-4264-AB8A-17C393E9140C}.Release|x86.ActiveCfg = Release|Any CPU + {0385564F-07B4-4264-AB8A-17C393E9140C}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -639,5 +649,9 @@ Global {887630A3-4B1D-40EA-B8B3-2D842E9C40DB} = {35636A82-401A-4C3A-B2AB-EB7DC5E9C268} {35636A82-401A-4C3A-B2AB-EB7DC5E9C268} = {F7876C9B-FB6A-4EFB-B058-D6967DB75FB2} {FF76BD3C-5E0A-4752-B6C3-044F6E15719B} = {35636A82-401A-4C3A-B2AB-EB7DC5E9C268} + {0385564F-07B4-4264-AB8A-17C393E9140C} = {F6DAEE9A-8BE1-4C4A-BC83-09215517C7DA} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {48EDBBBE-C8EE-4E3C-8B19-97184A487B37} EndGlobalSection EndGlobal diff --git a/vsintegration/src/FSharp.Editor/LanguageService/LanguageService.fs b/vsintegration/src/FSharp.Editor/LanguageService/LanguageService.fs index 0b2d64bccb9..97d443fb8f9 100644 --- a/vsintegration/src/FSharp.Editor/LanguageService/LanguageService.fs +++ b/vsintegration/src/FSharp.Editor/LanguageService/LanguageService.fs @@ -164,7 +164,7 @@ type internal FSharpProjectOptionsManager this.AddOrUpdateProject(projectId, (fun isRefresh -> let extraProjectInfo = Some(box workspace) let tryGetOptionsForReferencedProject f = f |> tryGetOrCreateProjectId |> Option.bind this.TryGetOptionsForProject - let referencedProjects, options = ProjectSitesAndFiles.GetProjectOptionsForProjectSite(Settings.LanguageServicePerformance.EnableInMemoryCrossProjectReferences, tryGetOptionsForReferencedProject, site, site.ProjectFileName(), extraProjectInfo, serviceProvider, true) + let referencedProjects, options = ProjectSitesAndFiles.GetProjectOptionsForProjectSite(Settings.LanguageServicePerformance.EnableInMemoryCrossProjectReferences, tryGetOptionsForReferencedProject, site, site.ProjectFileName, extraProjectInfo, serviceProvider, true) let referencedProjectIds = referencedProjects |> Array.choose tryGetOrCreateProjectId checkerProvider.Checker.InvalidateConfiguration(options, startBackgroundCompileIfAlreadySeen = not isRefresh, userOpName= userOpName + ".UpdateProjectInfo") referencedProjectIds, options)) @@ -231,21 +231,22 @@ type internal FSharpProjectOptionsManager Some(reporter:> Microsoft.VisualStudio.Shell.Interop.IVsLanguageServiceBuildErrorReporter2) {new Microsoft.VisualStudio.FSharp.LanguageService.IProjectSite with - member __.SourceFilesOnDisk() = this.GetProjectInfo(project.FilePath) |> fst - member __.DescriptionOfProject() = project.Name - member __.CompilerFlags() = + member __.CompilationSourceFiles = this.GetProjectInfo(project.FilePath) |> fst + member __.CompilationOptions = let _,references,options = this.GetProjectInfo(project.FilePath) Array.concat [options; references |> Array.map(fun r -> "-r:" + r)] - member __.ProjectFileName() = project.FilePath + member __.CompilationReferences = this.GetProjectInfo(project.FilePath) |> thrd + member site.CompilationBinOutputPath = site.CompilationOptions |> Array.tryPick (fun s -> if s.StartsWith("-o:") then Some s.[3..] else None) + member __.Description = project.Name + member __.ProjectFileName = project.FilePath member __.AdviseProjectSiteChanges(_,_) = () member __.AdviseProjectSiteCleaned(_,_) = () member __.AdviseProjectSiteClosed(_,_) = () member __.IsIncompleteTypeCheckEnvironment = false member __.TargetFrameworkMoniker = "" - member __.ProjectGuid = project.Id.Id.ToString() + member __.ProjectGuid = project.Id.Id.ToString() member __.LoadTime = System.DateTime.Now member __.ProjectProvider = Some iProvideProjectSite - member __.AssemblyReferences() = this.GetProjectInfo(project.FilePath) |> thrd member __.BuildErrorReporter with get () = errorReporter and set (v) = errorReporter <- v } @@ -459,17 +460,20 @@ type let theme = package.ComponentModel.DefaultExportProvider.GetExport().Value theme.SetColors() - - /// Sync the information for the project - member __.SyncProject(project: AbstractProject, projectContext: IWorkspaceProjectContext, site: IProjectSite, workspace, forceUpdate, userOpName) = + + /// Sync the Roslyn information for the project held in 'projectContext' to match the information given by 'site'. + /// Also sync the info in ProjectInfoManager if necessary. + member this.SyncProject(project: AbstractProject, projectContext: IWorkspaceProjectContext, site: IProjectSite, workspace, forceUpdate, userOpName) = let wellFormedFilePathSetIgnoreCase (paths: seq) = - HashSet(paths |> Seq.filter isPathWellFormed |> Seq.map (fun s -> try System.IO.Path.GetFullPath(s) with _ -> s), StringComparer.OrdinalIgnoreCase) - - let updatedFiles = site.SourceFilesOnDisk() |> wellFormedFilePathSetIgnoreCase - let originalFiles = project.GetCurrentDocuments() |> Seq.map (fun file -> file.FilePath) |> wellFormedFilePathSetIgnoreCase + HashSet(paths |> Seq.filter isPathWellFormed |> Seq.map (fun s -> try Path.GetFullPath(s) with _ -> s), StringComparer.OrdinalIgnoreCase) let mutable updated = forceUpdate + // Sync the source files in projectContext. Note that these source files are __not__ maintained in order in projectContext + // as edits are made. It seems this is ok because the source file list is only used to drive roslyn per-file checking. + let updatedFiles = site.CompilationSourceFiles |> wellFormedFilePathSetIgnoreCase + let originalFiles = project.GetCurrentDocuments() |> Seq.map (fun file -> file.FilePath) |> wellFormedFilePathSetIgnoreCase + for file in updatedFiles do if not(originalFiles.Contains(file)) then projectContext.AddSourceFile(file) @@ -480,7 +484,7 @@ type projectContext.RemoveSourceFile(file) updated <- true - let updatedRefs = site.AssemblyReferences() |> wellFormedFilePathSetIgnoreCase + let updatedRefs = site.CompilationReferences |> wellFormedFilePathSetIgnoreCase let originalRefs = project.GetCurrentMetadataReferences() |> Seq.map (fun ref -> ref.FilePath) |> wellFormedFilePathSetIgnoreCase for ref in updatedRefs do @@ -493,8 +497,9 @@ type projectContext.RemoveMetadataReference(ref) updated <- true + // Update the project options association let ok,originalOptions = optionsAssociation.TryGetValue(projectContext) - let updatedOptions = site.CompilerFlags() + let updatedOptions = site.CompilationOptions if not ok || originalOptions <> updatedOptions then // OK, project options have changed, try to fake out Roslyn to convince it to reparse things. @@ -520,13 +525,12 @@ type let userOpName = userOpName + ".SetupProjectFile" let rec setup (site: IProjectSite) = let projectGuid = Guid(site.ProjectGuid) - let projectFileName = site.ProjectFileName() + let projectFileName = site.ProjectFileName let projectDisplayName = projectDisplayNameOf projectFileName let projectId = workspace.ProjectTracker.GetOrCreateProjectIdForPath(projectFileName, projectDisplayName) if isNull (workspace.ProjectTracker.GetProject projectId) then - projectInfoManager.UpdateProjectInfo(tryGetOrCreateProjectId workspace, projectId, site, userOpName) let projectContextFactory = package.ComponentModel.GetService(); let errorReporter = ProjectExternalErrorReporter(projectId, "FS", this.SystemServiceProvider) @@ -542,27 +546,35 @@ type let projectContext = projectContextFactory.CreateProjectContext( - FSharpConstants.FSharpLanguageName, projectDisplayName, projectFileName, projectGuid, hierarchy, null, errorReporter) + FSharpConstants.FSharpLanguageName, + projectDisplayName, + projectFileName, + projectGuid, + hierarchy, + Option.toObj site.CompilationBinOutputPath, + errorReporter) let project = projectContext :?> AbstractProject - this.SyncProject(project, projectContext, site, workspace, forceUpdate=false, userOpName=userOpName) + // Sync IProjectSite --> projectContext, and IProjectSite --> ProjectInfoManage + this.SyncProject(project, projectContext, site, workspace, forceUpdate=true, userOpName=userOpName) site.BuildErrorReporter <- Some (errorReporter :> Microsoft.VisualStudio.Shell.Interop.IVsLanguageServiceBuildErrorReporter2) + // TODO: consider forceUpdate = false here. forceUpdate=true may be causing repeated computation? site.AdviseProjectSiteChanges(FSharpConstants.FSharpLanguageServiceCallbackName, AdviseProjectSiteChanges(fun () -> this.SyncProject(project, projectContext, site, workspace, forceUpdate=true, userOpName="AdviseProjectSiteChanges."+userOpName))) + site.AdviseProjectSiteClosed(FSharpConstants.FSharpLanguageServiceCallbackName, AdviseProjectSiteChanges(fun () -> projectInfoManager.ClearInfoForProject(project.Id) optionsAssociation.Remove(projectContext) |> ignore project.Disconnect())) + for referencedSite in ProjectSitesAndFiles.GetReferencedProjectSites (site, this.SystemServiceProvider) do - let referencedProjectId = setup referencedSite - project.AddProjectReference(ProjectReference referencedProjectId) + setup referencedSite - projectId - setup (siteProvider.GetProjectSite()) |> ignore + setup (siteProvider.GetProjectSite()) member this.SetupStandAloneFile(fileName: string, fileContents: string, workspace: VisualStudioWorkspaceImpl, hier: IVsHierarchy) = let loadTime = DateTime.Now diff --git a/vsintegration/src/FSharp.LanguageService/BackgroundRequests.fs b/vsintegration/src/FSharp.LanguageService/BackgroundRequests.fs index 69ac749e232..1fe078c044e 100644 --- a/vsintegration/src/FSharp.LanguageService/BackgroundRequests.fs +++ b/vsintegration/src/FSharp.LanguageService/BackgroundRequests.fs @@ -99,7 +99,7 @@ type internal FSharpLanguageServiceBackgroundRequests_DEPRECATED let projectSite = ProjectSitesAndFiles.CreateProjectSiteForScript(fileName, referencedProjectFileNames, checkOptions) { ProjectSite = projectSite CheckOptions = checkOptions - ProjectFileName = projectSite.ProjectFileName() + ProjectFileName = projectSite.ProjectFileName FSharpChecker = checker Colorizer = lazy getColorizer(view) } Some data @@ -109,7 +109,7 @@ type internal FSharpLanguageServiceBackgroundRequests_DEPRECATED let projectSite = getProjectSitesAndFiles().FindOwningProject_DEPRECATED(rdt,fileName) let enableInMemoryCrossProjectReferences = true let _, checkOptions = ProjectSitesAndFiles.GetProjectOptionsForProjectSite(enableInMemoryCrossProjectReferences, (fun _ -> None), projectSite, fileName, None, getServiceProvider(), false) - let projectFileName = projectSite.ProjectFileName() + let projectFileName = projectSite.ProjectFileName let data = { ProjectSite = projectSite CheckOptions = checkOptions diff --git a/vsintegration/src/FSharp.LanguageService/FSharpSource.fs b/vsintegration/src/FSharp.LanguageService/FSharpSource.fs index cba2beaad66..c99ceb2ea39 100644 --- a/vsintegration/src/FSharp.LanguageService/FSharpSource.fs +++ b/vsintegration/src/FSharp.LanguageService/FSharpSource.fs @@ -350,7 +350,7 @@ type internal FSharpSource_DEPRECATED(service:LanguageService_DEPRECATED, textLi [| match iSource.ProjectSite with | Some pi -> - yield! pi.CompilerFlags () |> Array.filter(fun flag -> flag.StartsWith("--define:")) + yield! pi.CompilationOptions |> Array.filter(fun flag -> flag.StartsWith("--define:")) | None -> () yield "--noframework" diff --git a/vsintegration/src/FSharp.LanguageService/IProjectSite.fs b/vsintegration/src/FSharp.LanguageService/IProjectSite.fs index 9d1d32d309e..dfdc99d8ec4 100644 --- a/vsintegration/src/FSharp.LanguageService/IProjectSite.fs +++ b/vsintegration/src/FSharp.LanguageService/IProjectSite.fs @@ -17,25 +17,32 @@ type internal IProvideProjectSite = and internal IProjectSite = /// List of files in the project. In the correct order. - abstract SourceFilesOnDisk : unit -> string[] + abstract CompilationSourceFiles : string[] - /// Flags that the compiler would need to understand how to compile. - abstract CompilerFlags : unit -> string[] + /// Flags that the compiler would need to understand how to compile. Includes '-r' + /// options but not source files + abstract CompilationOptions : string[] + + /// The normalized '-r:' assembly references, without the '-r:' + abstract CompilationReferences : string [] + + /// The '-o:' output bin path, without the '-o:' + abstract CompilationBinOutputPath : string option /// Register for notifications for when the above change - abstract AdviseProjectSiteChanges : (*callbackOwnerKey*)string * AdviseProjectSiteChanges -> unit + abstract AdviseProjectSiteChanges : callbackOwnerKey: string * AdviseProjectSiteChanges -> unit /// Register for notifications when project is cleaned/rebuilt (and thus any live TypeProviders should be refreshed) - abstract AdviseProjectSiteCleaned : (*callbackOwnerKey*)string * AdviseProjectSiteChanges -> unit + abstract AdviseProjectSiteCleaned : callbackOwnerKey: string * AdviseProjectSiteChanges -> unit // Register for notifications when project is closed. - abstract AdviseProjectSiteClosed : (*callbackOwnerKey*)string * AdviseProjectSiteChanges -> unit + abstract AdviseProjectSiteClosed : callbackOwnerKey: string * AdviseProjectSiteChanges -> unit /// A user-friendly description of the project. Used only for developer/DEBUG tooltips and such. - abstract DescriptionOfProject : unit -> string + abstract Description : string /// The name of the project file. - abstract ProjectFileName : unit -> string + abstract ProjectFileName : string /// The error list task reporter abstract BuildErrorReporter : Microsoft.VisualStudio.Shell.Interop.IVsLanguageServiceBuildErrorReporter2 option with get, set @@ -55,4 +62,4 @@ and internal IProjectSite = abstract ProjectProvider : IProvideProjectSite option - abstract AssemblyReferences : unit -> string [] + diff --git a/vsintegration/src/FSharp.LanguageService/ProjectSitesAndFiles.fs b/vsintegration/src/FSharp.LanguageService/ProjectSitesAndFiles.fs index a7367999ab4..8277ad6c006 100644 --- a/vsintegration/src/FSharp.LanguageService/ProjectSitesAndFiles.fs +++ b/vsintegration/src/FSharp.LanguageService/ProjectSitesAndFiles.fs @@ -49,10 +49,14 @@ type private IHaveCheckOptions = /// Convert from FSharpProjectOptions into IProjectSite. type private ProjectSiteOfScriptFile(filename:string, referencedProjectFileNames, checkOptions : FSharpProjectOptions) = interface IProjectSite with - override this.SourceFilesOnDisk() = checkOptions.SourceFiles - override this.DescriptionOfProject() = sprintf "Script Closure at Root %s" filename - override this.CompilerFlags() = checkOptions.OtherOptions - override this.ProjectFileName() = checkOptions.ProjectFileName + override this.Description = sprintf "Script Closure at Root %s" filename + override this.CompilationSourceFiles = checkOptions.SourceFiles + override this.CompilationOptions = checkOptions.OtherOptions + override this.CompilationReferences = + checkOptions.OtherOptions + |> Array.choose (fun flag -> if flag.StartsWith("-r:") then Some flag.[3..] else None) + override this.CompilationBinOutputPath = None + override this.ProjectFileName = checkOptions.ProjectFileName override this.BuildErrorReporter with get() = None and set _v = () override this.AdviseProjectSiteChanges(_,_) = () override this.AdviseProjectSiteCleaned(_,_) = () @@ -62,7 +66,6 @@ type private ProjectSiteOfScriptFile(filename:string, referencedProjectFileNames override this.ProjectGuid = "" override this.LoadTime = checkOptions.LoadTime override this.ProjectProvider = None - override this.AssemblyReferences() = [||] interface IHaveCheckOptions with override this.OriginalCheckOptions() = (referencedProjectFileNames, checkOptions) @@ -73,7 +76,7 @@ type private ProjectSiteOfScriptFile(filename:string, referencedProjectFileNames /// By design, these are never going to typecheck because there is no affiliated references. /// We show many squiggles in this case because they're not particularly informational. type private ProjectSiteOfSingleFile(sourceFile) = - // CompilerFlags() gets called a lot, so pre-compute what we can + // CompilationOptions gets called a lot, so pre-compute what we can static let compilerFlags = let flags = ["--noframework";"--warn:3"] let assumeDotNetFramework = true @@ -85,10 +88,12 @@ type private ProjectSiteOfSingleFile(sourceFile) = let projectFileName = sourceFile + ".orphan.fsproj" interface IProjectSite with - override this.SourceFilesOnDisk() = [|sourceFile|] - override this.DescriptionOfProject() = "Orphan File Project" - override this.CompilerFlags() = compilerFlags - override this.ProjectFileName() = projectFileName + override this.Description = projectFileName + override this.CompilationSourceFiles = [|sourceFile|] + override this.CompilationOptions = compilerFlags + override this.CompilationReferences = compilerFlags |> Array.choose (fun flag -> if flag.StartsWith("-r:") then Some flag.[3..] else None) + override this.CompilationBinOutputPath = None + override this.ProjectFileName = projectFileName override this.BuildErrorReporter with get() = None and set _v = () override this.AdviseProjectSiteChanges(_,_) = () override this.AdviseProjectSiteCleaned(_,_) = () @@ -98,7 +103,6 @@ type private ProjectSiteOfSingleFile(sourceFile) = override this.ProjectGuid = "" override this.LoadTime = new DateTime(2000,1,1) // any constant time is fine, orphan files do not interact with reloading based on update time override this.ProjectProvider = None - override this.AssemblyReferences() = [||] override x.ToString() = sprintf "ProjectSiteOfSingleFile(%s)" sourceFile @@ -174,9 +178,9 @@ type internal ProjectSitesAndFiles() = else [| |], [| |] let options = - {ProjectFileName = projectSite.ProjectFileName() - SourceFiles = projectSite.SourceFilesOnDisk() - OtherOptions = projectSite.CompilerFlags() + {ProjectFileName = projectSite.ProjectFileName + SourceFiles = projectSite.CompilationSourceFiles + OtherOptions = projectSite.CompilationOptions ReferencedProjects = referencedProjectOptions IsIncompleteTypeCheckEnvironment = projectSite.IsIncompleteTypeCheckEnvironment UseScriptResolutionRules = SourceFile.MustBeSingleFileProject fileName @@ -257,7 +261,7 @@ type internal ProjectSitesAndFiles() = | Some site -> site | None -> ProjectSitesAndFiles.ProjectSiteOfSingleFile(filename) - CompilerEnvironment.GetCompilationDefinesForEditing(filename,site.CompilerFlags() |> Array.toList) + CompilerEnvironment.GetCompilationDefinesForEditing(filename,site.CompilationOptions |> Array.toList) member art.TryFindOwningProject_DEPRECATED(rdt:IVsRunningDocumentTable, filename) = @@ -267,11 +271,7 @@ type internal ProjectSitesAndFiles() = | Some(hier, _textLines) -> match tryGetProjectSite(hier) with | Some(site) -> -#if DEBUG - for src in site.SourceFilesOnDisk() do - Debug.Assert(Path.GetFullPath(src) = src, "SourceFilesOnDisk reported a filename that was not in canonical format") -#endif - if site.SourceFilesOnDisk() |> Array.exists (fun src -> StringComparer.OrdinalIgnoreCase.Equals(src,filename)) then + if site.CompilationSourceFiles |> Array.exists (fun src -> StringComparer.OrdinalIgnoreCase.Equals(src,filename)) then Some site else None diff --git a/vsintegration/src/FSharp.ProjectSystem.Base/Project/ProjectNode.cs b/vsintegration/src/FSharp.ProjectSystem.Base/Project/ProjectNode.cs index e7c67269c67..bf1d827a2ef 100644 --- a/vsintegration/src/FSharp.ProjectSystem.Base/Project/ProjectNode.cs +++ b/vsintegration/src/FSharp.ProjectSystem.Base/Project/ProjectNode.cs @@ -1643,7 +1643,7 @@ internal override int QueryStatusOnNode(Guid cmdGroup, uint cmd, IntPtr pCmdText result |= QueryStatusResult.SUPPORTED; if (options == null) { - var currentConfigName = FetchCurrentConfigurationName(); + var currentConfigName = GetCurrentConfigurationName(); if (currentConfigName != null) { GetProjectOptions(currentConfigName.Value); @@ -1705,7 +1705,7 @@ public override int ExecCommandOnNode(Guid cmdGroup, uint cmd, uint nCmdexecopt, { if (options == null) { - var currentConfigName = FetchCurrentConfigurationName(); + var currentConfigName = GetCurrentConfigurationName(); if (currentConfigName != null) { GetProjectOptions(currentConfigName.Value); @@ -4070,7 +4070,7 @@ internal string GetKeyOutputForGroup(IVsHierarchy hier, string groupName) private void TellMSBuildCurrentSolutionConfiguration() { - var canonicalCfgNameOpt = FetchCurrentConfigurationName(); + var canonicalCfgNameOpt = GetCurrentConfigurationName(); if (canonicalCfgNameOpt == null) return; var canonicalCfgName = canonicalCfgNameOpt.Value; @@ -4087,7 +4087,7 @@ private void TellMSBuildCurrentSolutionConfiguration() this.UpdateMSBuildState(); } - private ConfigCanonicalName? FetchCurrentConfigurationName() + private ConfigCanonicalName? GetCurrentConfigurationName() { if (Site == null) return null; @@ -4104,6 +4104,18 @@ private void TellMSBuildCurrentSolutionConfiguration() return new ConfigCanonicalName(cfgName); } + internal string GetCurrentOutputAssembly() + { + var currentConfigName = GetCurrentConfigurationName(); + if (currentConfigName != null) + { + GetProjectOptions(currentConfigName.Value); + if (options != null) + return options.OutputAssembly; + } + return null; + } + /// /// Overloaded method. Invokes MSBuild using the default configuration and does without logging on the output window pane. /// diff --git a/vsintegration/src/FSharp.ProjectSystem.FSharp/Project.fs b/vsintegration/src/FSharp.ProjectSystem.FSharp/Project.fs index f78462de440..56c00e8646f 100644 --- a/vsintegration/src/FSharp.ProjectSystem.FSharp/Project.fs +++ b/vsintegration/src/FSharp.ProjectSystem.FSharp/Project.fs @@ -88,26 +88,30 @@ namespace rec Microsoft.VisualStudio.FSharp.ProjectSystem // An IProjectSite object with hot-swappable inner implementation type internal DynamicProjectSite(origInnerImpl : Microsoft.VisualStudio.FSharp.LanguageService.IProjectSite) = + let mutable inner = origInnerImpl + member x.SetImplementation newInner = inner <- newInner + // This interface is thread-safe, assuming "inner" is thread-safe interface Microsoft.VisualStudio.FSharp.LanguageService.IProjectSite with - member ips.SourceFilesOnDisk() = inner.SourceFilesOnDisk() - member ips.DescriptionOfProject() = inner.DescriptionOfProject() - member ips.CompilerFlags() = inner.CompilerFlags() - member ips.ProjectFileName() = inner.ProjectFileName() - member ips.AdviseProjectSiteChanges(callbackOwnerKey,callback) = inner.AdviseProjectSiteChanges(callbackOwnerKey, callback) - member ips.AdviseProjectSiteCleaned(callbackOwnerKey,callback) = inner.AdviseProjectSiteCleaned(callbackOwnerKey, callback) - member ips.AdviseProjectSiteClosed(callbackOwnerKey,callback) = inner.AdviseProjectSiteClosed(callbackOwnerKey, callback) - member ips.BuildErrorReporter with get() = inner.BuildErrorReporter and set v = inner.BuildErrorReporter <- v - member ips.TargetFrameworkMoniker = inner.TargetFrameworkMoniker - member ips.ProjectGuid = inner.ProjectGuid - member ips.IsIncompleteTypeCheckEnvironment = false - member ips.LoadTime = inner.LoadTime - member ips.ProjectProvider = inner.ProjectProvider - member ips.AssemblyReferences() = inner.AssemblyReferences() - override x.ToString() = inner.ProjectFileName() + member __.Description = inner.Description + member __.CompilationSourceFiles = inner.CompilationSourceFiles + member __.CompilationOptions = inner.CompilationOptions + member __.CompilationReferences = inner.CompilationReferences + member __.CompilationBinOutputPath = inner.CompilationBinOutputPath + member __.ProjectFileName = inner.ProjectFileName + member __.AdviseProjectSiteChanges(callbackOwnerKey,callback) = inner.AdviseProjectSiteChanges(callbackOwnerKey, callback) + member __.AdviseProjectSiteCleaned(callbackOwnerKey,callback) = inner.AdviseProjectSiteCleaned(callbackOwnerKey, callback) + member __.AdviseProjectSiteClosed(callbackOwnerKey,callback) = inner.AdviseProjectSiteClosed(callbackOwnerKey, callback) + member __.BuildErrorReporter with get() = inner.BuildErrorReporter and set v = inner.BuildErrorReporter <- v + member __.TargetFrameworkMoniker = inner.TargetFrameworkMoniker + member __.ProjectGuid = inner.ProjectGuid + member __.IsIncompleteTypeCheckEnvironment = false + member __.LoadTime = inner.LoadTime + member __.ProjectProvider = inner.ProjectProvider + override x.ToString() = inner.ProjectFileName type internal ProjectSiteOptionLifetimeState = | Opening=1 // The project has been opened, but has not yet called Compile() to compute sources/flags @@ -408,11 +412,12 @@ namespace rec Microsoft.VisualStudio.FSharp.ProjectSystem let mutable inMidstOfReloading = false - // WARNING: Please avoid renaming this field or the containing type. The Visual F# Power Tools currently - // use reflection to access this field to extract project sources and flags accurately from F# projects. - // See https://github.com/fsprojects/VisualFSharpPowerTools/blob/58b8e409ee6836a39b22284740706d2cf488aabe/src/FSharpVSPowerTools.Logic/ProjectProvider.fs#L42 - // for example. If necessary, this can be changed - but please just try to avoid doing a gratuitous rename. - let mutable sourcesAndFlags : option<(array * array)> = None + let mutable sourcesAndFlags : option<(string[] * string[])> = None + + let mutable normalizedRefs : string[] option = None + + let mutable binOutputPath : string option = None + #if DEBUG let logger = new Microsoft.Build.Logging.ConsoleLogger(Microsoft.Build.Framework.LoggerVerbosity.Diagnostic, (fun s -> Trace.WriteLine("MSBuild: " + s)), @@ -530,7 +535,7 @@ namespace rec Microsoft.VisualStudio.FSharp.ProjectSystem this.SetProjectProperty(ProjectFileConstants.TargetFSharpCoreVersion, v) - let buildResult = this.Build(MsBuildTarget.ResolveAssemblyReferences); + let buildResult = this.Build(MsBuildTarget.ResolveAssemblyReferences) for asmNode in System.Linq.Enumerable.OfType(this.GetReferenceContainer().EnumReferences()) do if (AssemblyReferenceNode.IsFSharpCoreReference asmNode) then @@ -1286,42 +1291,30 @@ namespace rec Microsoft.VisualStudio.FSharp.ProjectSystem result // Fulfill HostObject contract with Fsc task, and enable 'capture' of compiler flags for the project. -#if FX_NO_CONVERTER - member x.Compile(compile:Func, flags:string[], sources:string[]) = -#else member x.Compile(compile:System.Converter, flags:string[], sources:string[]) = -#endif // Note: This method may be called from non-UI thread! The Fsc task in FSharp.Build.dll invokes this method via reflection, and // the Fsc task is typically created by MSBuild on a background thread. So be careful. #if DEBUG compileWasActuallyCalled <- true #endif - let normalizedSources = sources |> Array.map (fun fn -> System.IO.Path.GetFullPath(System.IO.Path.Combine(x.ProjectFolder, fn))) - let r = (normalizedSources, flags) - sourcesAndFlags <- Some(r) + let updatedNormalizedSources = sources |> Array.map (fun fn -> System.IO.Path.GetFullPath(System.IO.Path.Combine(x.ProjectFolder, fn))) + let updatedNormalizedRefs = flags |> Array.choose (fun flag -> if flag.StartsWith("-r:") then Some flag.[3..] else None) |> Array.map (fun fn -> Path.GetFullPath(Path.Combine(x.ProjectFolder, fn))) + sourcesAndFlags <- Some (updatedNormalizedSources, flags) + normalizedRefs <- Some updatedNormalizedRefs + binOutputPath <- x.GetCurrentOutputAssembly() |> Option.ofObj + if projectSite.State = ProjectSiteOptionLifetimeState.Opening then // This is the first time, so set up interface for language service to talk to us projectSite.Open(x.CreateRunningProjectSite()) if actuallyBuild then -#if FX_NO_CONVERTER - compile.Invoke() -#else compile.Invoke(0) -#endif else 0 - // returns an array of all "foo"s of form: - member private x.ComputeCompileItems() = - FSharpProjectNode.ComputeCompileItems(x.BuildProject, x.ProjectFolder) - static member ComputeCompileItems(buildProject, projectFolder) = - [| - for i in buildProject.Items do - if i.ItemType = "Compile" then - yield System.IO.Path.GetFullPath(System.IO.Path.Combine(projectFolder, i.EvaluatedInclude)) - |] - member x.GetCompileItems() = let sources,_ = sourcesAndFlags.Value in sources - member x.GetCompileFlags() = let _,flags = sourcesAndFlags.Value in flags + member __.CompilationSourceFiles = match sourcesAndFlags with None -> [| |] | Some (sources,_) -> sources + member __.CompilationOptions = match sourcesAndFlags with None -> [| |] | Some (_,flags) -> flags + member __.CompilationReferences = match normalizedRefs with None -> [| |] | Some refs -> refs + member __.CompilationBinOutputPath = binOutputPath override x.ComputeSourcesAndFlags() = @@ -1340,7 +1333,7 @@ namespace rec Microsoft.VisualStudio.FSharp.ProjectSystem } |> WaitDialog.start x.Site - // REVIEW CompilerFlags will be stale since last 'save' of MSBuild .fsproj file - can we do better? + // REVIEW CompilationOptions will be stale since last 'save' of MSBuild .fsproj file - can we do better? try actuallyBuild <- false x.SetCurrentConfiguration() @@ -1440,73 +1433,67 @@ namespace rec Microsoft.VisualStudio.FSharp.ProjectSystem member private x.CreateRunningProjectSite() = let creationTime = System.DateTime.Now { new Microsoft.VisualStudio.FSharp.LanguageService.IProjectSite with - // Note: these methods get called by language service from an arbitrary thread, but the things they - // access (x.GetCompileItems, x.Caption, sourcesAndFlags) are all thread-safe. - member ips.SourceFilesOnDisk() = x.GetCompileItems() - member ips.DescriptionOfProject() = - let sources,flags = sourcesAndFlags.Value - sprintf "Project System: flags(%A) sources:\n%A" flags sources - member ips.CompilerFlags() = x.GetCompileFlags() - member ips.ProjectFileName() = MSBuildProject.GetFullPath(x.BuildProject) - member ips.BuildErrorReporter with get() = buildErrorReporter and set v = buildErrorReporter <- v - member this.AdviseProjectSiteChanges(callbackOwnerKey,callback) = - sourcesAndFlagsNotifier.Advise(callbackOwnerKey,callback) - member this.AdviseProjectSiteCleaned(callbackOwnerKey,callback) = - cleanNotifier.Advise(callbackOwnerKey,callback) - member this.AdviseProjectSiteClosed(callbackOwnerKey,callback) = - closeNotifier.Advise(callbackOwnerKey,callback) - member this.IsIncompleteTypeCheckEnvironment = false - member this.TargetFrameworkMoniker = x.GetTargetFrameworkMoniker() - member this.ProjectGuid = x.GetProjectGuid() - member this.LoadTime = creationTime - member this.ProjectProvider = Some (x :> IProvideProjectSite) - member this.AssemblyReferences() = - x.GetReferenceContainer().EnumReferences() - |> Seq.choose ( - function - | :? AssemblyReferenceNode as arn -> Some arn.Url - | _ -> None - ) - |> Array.ofSeq + + member __.CompilationSourceFiles = x.CompilationSourceFiles + member __.CompilationOptions = x.CompilationOptions + member __.CompilationReferences = x.CompilationReferences + member __.CompilationBinOutputPath = x.CompilationBinOutputPath + + member __.Description = + match sourcesAndFlags with + | Some (sources,flags) -> sprintf "Project System: flags(%A) sources:\n%A" flags sources + | None -> sprintf "Project System, no flags available" + + member __.ProjectFileName = MSBuildProject.GetFullPath(x.BuildProject) + + member __.BuildErrorReporter + with get() = buildErrorReporter + and set v = buildErrorReporter <- v + + member __.AdviseProjectSiteChanges(callbackOwnerKey,callback) = sourcesAndFlagsNotifier.Advise(callbackOwnerKey,callback) + member __.AdviseProjectSiteCleaned(callbackOwnerKey,callback) = cleanNotifier.Advise(callbackOwnerKey,callback) + member __.AdviseProjectSiteClosed(callbackOwnerKey,callback) = closeNotifier.Advise(callbackOwnerKey,callback) + member __.IsIncompleteTypeCheckEnvironment = false + member __.TargetFrameworkMoniker = x.GetTargetFrameworkMoniker() + member __.ProjectGuid = x.GetProjectGuid() + member __.LoadTime = creationTime + member __.ProjectProvider = Some (x :> IProvideProjectSite) + } // Snapshot-capture relevent values from "this", and returns an IProjectSite // that does _not_ reference "this" to get its information. + // CreateStaticProjectSite can be called on a project that failed to load (as in Close) member private x.CreateStaticProjectSite() = - // CreateStaticProjectSite can be called on a project that failed to load (as in Close) - let compileItems,flags = - match sourcesAndFlags with - | None -> Array.create 0 "", Array.create 0 "" - | Some(sources,flags) -> sources, flags - let caption = x.Caption + let outputPath = x.CompilationBinOutputPath + let sourceFiles = x.CompilationSourceFiles + let options = x.CompilationOptions + let refs = x.CompilationReferences + let description = x.Caption let mutable staticBuildErrorReporter = buildErrorReporter let projFileName = MSBuildProject.GetFullPath(x.BuildProject) let targetFrameworkMoniker = x.GetTargetFrameworkMoniker() - let creationTime = System.DateTime.Now - let assemblyReferences = - x.GetReferenceContainer().EnumReferences() - |> Seq.choose ( - function - | :? AssemblyReferenceNode as arn -> Some arn.Url - | _ -> None - ) - |> Array.ofSeq + let creationTime = DateTime.Now + // This object is thread-safe { new Microsoft.VisualStudio.FSharp.LanguageService.IProjectSite with - member ips.SourceFilesOnDisk() = compileItems - member ips.DescriptionOfProject() = caption - member ips.CompilerFlags() = flags - member ips.ProjectFileName() = projFileName - member ips.BuildErrorReporter with get() = staticBuildErrorReporter and set v = staticBuildErrorReporter <- v - member this.AdviseProjectSiteChanges(_,_) = () - member this.AdviseProjectSiteCleaned(_,_) = () - member this.AdviseProjectSiteClosed(_,_) = () - member this.IsIncompleteTypeCheckEnvironment = false - member this.TargetFrameworkMoniker = targetFrameworkMoniker - member this.ProjectGuid = x.GetProjectGuid() - member this.LoadTime = creationTime - member this.ProjectProvider = Some (x :> IProvideProjectSite) - member this.AssemblyReferences() = assemblyReferences + member __.Description = description + member __.CompilationSourceFiles = sourceFiles + member __.CompilationOptions = options + member __.CompilationReferences = refs + member __.CompilationBinOutputPath = outputPath + member __.ProjectFileName = projFileName + member __.BuildErrorReporter + with get() = staticBuildErrorReporter + and set v = staticBuildErrorReporter <- v + member __.AdviseProjectSiteChanges(_,_) = () + member __.AdviseProjectSiteCleaned(_,_) = () + member __.AdviseProjectSiteClosed(_,_) = () + member __.IsIncompleteTypeCheckEnvironment = false + member __.TargetFrameworkMoniker = targetFrameworkMoniker + member __.ProjectGuid = x.GetProjectGuid() + member __.LoadTime = creationTime + member __.ProjectProvider = Some (x :> IProvideProjectSite) } // let the language service ask us questions diff --git a/vsintegration/tests/Salsa/FSharpLanguageServiceTestable.fs b/vsintegration/tests/Salsa/FSharpLanguageServiceTestable.fs index 23b24f741d9..f0812f1d566 100644 --- a/vsintegration/tests/Salsa/FSharpLanguageServiceTestable.fs +++ b/vsintegration/tests/Salsa/FSharpLanguageServiceTestable.fs @@ -115,8 +115,8 @@ type internal FSharpLanguageServiceTestable() as this = member this.OnProjectSettingsChanged(site:IProjectSite) = // The project may have changed its references. These would be represented as 'dependency files' of each source file. Each source file will eventually start listening // for changes to those dependencies, at which point we'll get OnDependencyFileCreateOrDelete notifications. Until then, though, we just 'make a note' that this project is out of date. - bgRequests.AddOutOfDateProjectFileName(site.ProjectFileName()) - for filename in site.SourceFilesOnDisk() do + bgRequests.AddOutOfDateProjectFileName(site.ProjectFileName) + for filename in site.CompilationSourceFiles do let rdt = this.ServiceProvider.RunningDocumentTable match this.ProjectSitesAndFiles.TryGetSourceOfFile_DEPRECATED(rdt,filename) with | Some source -> diff --git a/vsintegration/tests/Salsa/salsa.fs b/vsintegration/tests/Salsa/salsa.fs index 5e33c81e71c..305f77d94d6 100644 --- a/vsintegration/tests/Salsa/salsa.fs +++ b/vsintegration/tests/Salsa/salsa.fs @@ -43,19 +43,11 @@ module internal Salsa = { new System.IDisposable with member this.Dispose() = actuallyBuild <- true } member th.Results = capturedFlags, capturedSources -#if FX_NO_CONVERTER - member th.Compile(compile:Func, flags:string[], sources:string[]) = -#else member th.Compile(compile:System.Converter, flags:string[], sources:string[]) = -#endif capturedFlags <- flags capturedSources <- sources if actuallyBuild then -#if FX_NO_CONVERTER - compile.Invoke() -#else compile.Invoke(0) -#endif else 0 interface ITaskHost @@ -272,23 +264,17 @@ module internal Salsa = interface IProjectSite with - member this.SourceFilesOnDisk() = - let flags = GetFlags() - flags.sources - |> List.map(fun s->Path.Combine(projectPath, s)) |> List.toArray + member this.CompilationSourceFiles = + GetFlags().sources |> List.map(fun s->Path.Combine(projectPath, s)) |> List.toArray - member this.DescriptionOfProject() = + member this.Description = let flags = GetFlags() - try sprintf "MSBuild Flags:%A\n%A" ((this :> IProjectSite).CompilerFlags()) flags + try sprintf "MSBuild Flags:%A" flags with e -> sprintf "%A" e - member this.CompilerFlags() = - let flags = GetFlags() - let result = flags.flags - result |> List.toArray + member this.CompilationOptions = GetFlags().flags |> List.toArray - member this.ProjectFileName() = - projectfile + member this.ProjectFileName = projectfile member this.BuildErrorReporter with get() = None and set _v = () member this.AdviseProjectSiteChanges(callbackOwnerKey,callback) = changeHandlers.[callbackOwnerKey] <- callback @@ -303,7 +289,8 @@ module internal Salsa = projectObj.GetProperty(ProjectFileConstants.ProjectGuid).EvaluatedValue member this.ProjectProvider = None - member this.AssemblyReferences() = [||] + member this.CompilationReferences = [||] + member this.CompilationBinOutputPath = GetFlags().flags |> List.tryPick (fun s -> if s.StartsWith("-o:") then Some s.[3..] else None) // Attempt to treat as MSBuild project. let internal NewMSBuildProjectSite(configurationFunc, platformFunc, msBuildProjectName) = diff --git a/vsintegration/tests/unittests/TestLib.ProjectSystem.fs b/vsintegration/tests/unittests/TestLib.ProjectSystem.fs index fae9cc04288..b353e548f72 100644 --- a/vsintegration/tests/unittests/TestLib.ProjectSystem.fs +++ b/vsintegration/tests/unittests/TestLib.ProjectSystem.fs @@ -296,14 +296,14 @@ type TheTests() = (expectation.Elements(), actual.Elements()) ||> Seq.iter2 (fun x y -> Match(x,y,initialContext+"\n")) Match(expectation, actual, "") - static member internal GetCompileItems (project : UnitTestingFSharpProjectNode) = + static member internal Sources (project : UnitTestingFSharpProjectNode) = let MakeRelativePath (file : string) = let projDir = project.ProjectFolder + "\\" if file.StartsWith(projDir) then file.Substring(projDir.Length) else file - project.GetCompileItems() |> Array.toList |> List.map MakeRelativePath + project.CompilationSourceFiles |> Array.toList |> List.map MakeRelativePath member internal this.MakeProjectAndDoWithProjectFileAndConfigChangeNotifierDispose(dispose : bool, compileItems : string list, references : string list, other : string, targetFramework : string, f : UnitTestingFSharpProjectNode -> _ -> string -> unit) = UIStuff.SetupSynchronizationContext() diff --git a/vsintegration/tests/unittests/Tests.Build.fs b/vsintegration/tests/unittests/Tests.Build.fs index fe3c8d0a7b5..e5bbddc166c 100644 --- a/vsintegration/tests/unittests/Tests.Build.fs +++ b/vsintegration/tests/unittests/Tests.Build.fs @@ -40,11 +40,7 @@ type MyLogger(f : string -> unit) = type FauxHostObject() = let mutable myFlags : string[] = null let mutable mySources : string[] = null -#if FX_NO_CONVERTER - member x.Compile(compile:Func, flags:string[], sources:string[]) = -#else member x.Compile(compile:System.Converter, flags:string[], sources:string[]) = -#endif myFlags <- flags mySources <- sources 0 diff --git a/vsintegration/tests/unittests/Tests.ProjectSystem.Configs.fs b/vsintegration/tests/unittests/Tests.ProjectSystem.Configs.fs index 19c125a4322..806140621f6 100644 --- a/vsintegration/tests/unittests/Tests.ProjectSystem.Configs.fs +++ b/vsintegration/tests/unittests/Tests.ProjectSystem.Configs.fs @@ -38,7 +38,7 @@ type Config() = (fun project ccn projFileName -> ccn((project :> IVsHierarchy), "Debug|x86") project.ComputeSourcesAndFlags() - let flags = project.GetCompileFlags() |> List.ofArray + let flags = project.CompilationOptions |> List.ofArray Assert.IsTrue(List.exists (fun s -> s = "--platform:x86") flags) () )) diff --git a/vsintegration/tests/unittests/Tests.ProjectSystem.Miscellaneous.fs b/vsintegration/tests/unittests/Tests.ProjectSystem.Miscellaneous.fs index 6111c4432f3..a90f9d28651 100644 --- a/vsintegration/tests/unittests/Tests.ProjectSystem.Miscellaneous.fs +++ b/vsintegration/tests/unittests/Tests.ProjectSystem.Miscellaneous.fs @@ -501,7 +501,7 @@ type Miscellaneous() = let ipps = project :> Microsoft.VisualStudio.FSharp.LanguageService.IProvideProjectSite let ips = ipps.GetProjectSite() let expected = [| |] // Ideal behavior is [|"foo.fs";"bar.fs"|], and we could choose to improve this in the future. For now we are just happy to now throw/crash. - let actual = ips.SourceFilesOnDisk() + let actual = ips.CompilationSourceFiles Assert.AreEqual(expected, actual, "project site did not report expected set of source files") ) @@ -619,7 +619,7 @@ type Miscellaneous() = let project = TheTests.CreateProject(file, "false", cnn, sp) try project.ComputeSourcesAndFlags() - let items = project.GetCompileItems() |> Array.toList + let items = project.CompilationSourceFiles |> Array.toList match items with | [ _; fn ] -> // first file is AssemblyAttributes.fs AssertEqual fileName fn diff --git a/vsintegration/tests/unittests/Tests.ProjectSystem.ProjectItems.fs b/vsintegration/tests/unittests/Tests.ProjectSystem.ProjectItems.fs index 72ed2e5068a..43a7857c8a5 100644 --- a/vsintegration/tests/unittests/Tests.ProjectSystem.ProjectItems.fs +++ b/vsintegration/tests/unittests/Tests.ProjectSystem.ProjectItems.fs @@ -24,8 +24,8 @@ type ProjectItems() = project.ComputeSourcesAndFlags() let containsSystemNumerics () = - project.GetCompileFlags() - |> Seq.exists (fun f -> f.IndexOf("System.Numerics") <> -1) + project.CompilationOptions + |> Array.exists (fun f -> f.IndexOf("System.Numerics") <> -1) let wasCalled = ref false Assert.IsTrue(containsSystemNumerics (), "Project should contains reference to System.Numerics")