Skip to content

Commit a7f333e

Browse files
committed
correct dependency files
1 parent d535a02 commit a7f333e

File tree

8 files changed

+168
-58
lines changed

8 files changed

+168
-58
lines changed

docs/content/react.fsx

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
(*** hide ***)
2+
#I "../../bin/v4.5/"
3+
(**
4+
Compiler Services: Reacting to Changes
5+
============================================
6+
7+
This tutorial discusses some technical aspects of how to make sure the F# compiler service is
8+
providing up-to-date results especially when hosted in an IDE. See also [project wide analysis](project.html)
9+
for information on project analysis.
10+
11+
> **NOTE:** The FSharp.Compiler.Service API is subject to change when later versions of the nuget package are published.
12+
13+
The logical results of all "Check" routines (``ParseAndCheckFileInProject``, ``GetBackgroundCheckResultsForFileInProject``,
14+
``TryGetRecentTypeCheckResultsForFile``, ``ParseAndCheckProject``) depend on results reported by the file system,
15+
especially the ``IFileSystem`` implementation described in the tutorial on [project wide analysis](project.html).
16+
Logically speaking, these results would be different if file system changes occur. For example,
17+
referenced DLLs may change on disk, or referenced files may change.
18+
19+
The ``FSharpChecker`` component from FSharp.Compiler.Service does _not_ actively "listen"
20+
to changes in the file system. However ``FSharpChecker`` _does_ repeatedly ask for
21+
time stamps from the file system which it uses to decide if recomputation is needed.
22+
FCS doesn’t listen for changes directly - for example, it creates no ``FileWatcher`` object (and the
23+
``IFileSystem`` API has no ability to create such objects). This is partly for legacy reasons,
24+
and partly because some hosts forbid the creation of FileWatcher objects.
25+
26+
In most cases the repeated timestamp requests are sufficient. If you don't actively
27+
listen for changes, then ``FSharpChecker`` will still do _approximately_
28+
the right thing, because it is asking for time stamps repeatedly. However, some updates on the file system
29+
(such as a DLL appearing after a build, or the user randomly pasting a file into a folder)
30+
may not actively be noticed by ``FSharpChecker`` until some operation happens to ask for a timestamp.
31+
By issuing fresh requests, you can ensure that FCS actively reassesses the state of play when
32+
stays up-to-date when changes are observed.
33+
34+
If you want to more actively listen for changes, then you should add watchers for the
35+
files specified in the ``DependencyFiles`` property of ``FSharpCheckFileResults`` and ``FSharpCheckProjectResults``.
36+
Here’s what you need to do:
37+
38+
* When your client notices an CHANGE event on a DependencyFile, it should schedule a refresh call to perform the ParseAndCheckFileInProject (or other operation) again.
39+
This will result in fresh FileSystem calls to compute time stamps.
40+
41+
* When your client notices an ADD event on a DependencyFile, it should call ``checker.InvalidateConfiguration``
42+
for all active projects in which the file occurs. This will result in fresh FileSystem calls to compute time
43+
stamps, and fresh calls to compute whether files exist.
44+
45+
* Generally clients don’t listen for DELETE events on files. Although it would be logically more consistent
46+
to do so, in practice it’s very irritating for a "project clean" to invalidate all intellisense and
47+
cause lots of red squiggles. Some source control tools also make a change by removing and adding files, which
48+
is best noticed as a single change event.
49+
50+
51+
52+
If your host happens to be Visual Studio, then this is one technique you can use:
53+
* Listeners should be associated with a visual source file buffer
54+
* Use fragments like this to watch the DependencyFiles:
55+
56+
// Get the service
57+
let vsFileWatch = fls.GetService(typeof<SVsFileChangeEx >) :?> IVsFileChangeEx
58+
59+
// Watch the Add and Change events
60+
let fileChangeFlags =
61+
uint32 (_VSFILECHANGEFLAGS.VSFILECHG_Add |||
62+
// _VSFILECHANGEFLAGS.VSFILECHG_Del ||| // don't listen for deletes - if a file (such as a 'Clean'ed project reference) is deleted, just keep using stale info
63+
_VSFILECHANGEFLAGS.VSFILECHG_Time)
64+
65+
// Advise on file changes...
66+
let cookie = Com.ThrowOnFailure1(vsFileWatch.AdviseFileChange(file, fileChangeFlags, changeEvents))
67+
68+
...
69+
70+
// Unadvise file changes...
71+
Com.ThrowOnFailure0(vsFileWatch.UnadviseFileChange(cookie))
72+
73+
74+
*)

src/fsharp/CompileOps.fs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1750,10 +1750,10 @@ type IRawFSharpAssemblyData =
17501750
abstract HasMatchingFSharpSignatureDataAttribute : ILGlobals -> bool
17511751

17521752
type TimeStampCache() =
1753-
let now = DateTime.Now
1753+
let cacheCreation = DateTime.Now
17541754
let files = Dictionary<string,DateTime>()
17551755
let projects = Dictionary<IProjectReference,DateTime>(HashIdentity.Reference)
1756-
member x.Now = now
1756+
member x.CacheCreation = cacheCreation
17571757
member x.Files = files
17581758
member x.Projects = projects
17591759

src/fsharp/CompileOps.fsi

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ type IRawFSharpAssemblyData =
169169

170170
type TimeStampCache =
171171
new : unit -> TimeStampCache
172-
member Now: DateTime
172+
member CacheCreation: DateTime
173173
member Files: Dictionary<string,DateTime>
174174
member Projects:Dictionary<IProjectReference,DateTime>
175175

src/fsharp/vs/IncrementalBuild.fs

Lines changed: 27 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1021,6 +1021,7 @@ type TypeCheckAccumulator =
10211021
tcSymbolUses: TcSymbolUses list
10221022
topAttribs:TopAttribs option
10231023
typedImplFiles:TypedImplFile list
1024+
tcDependencyFiles: string list
10241025
tcErrors:(PhasedError * FSharpErrorSeverity) list } // errors=true, warnings=false
10251026

10261027

@@ -1115,6 +1116,7 @@ type PartialCheckResults =
11151116
Errors: (PhasedError * FSharpErrorSeverity) list
11161117
TcResolutions: TcResolutions list
11171118
TcSymbolUses: TcSymbolUses list
1119+
TcDependencyFiles: string list
11181120
TopAttribs: TopAttribs option
11191121
TimeStamp: System.DateTime }
11201122

@@ -1127,6 +1129,7 @@ type PartialCheckResults =
11271129
Errors = tcAcc.tcErrors
11281130
TcResolutions = tcAcc.tcResolutions
11291131
TcSymbolUses = tcAcc.tcSymbolUses
1132+
TcDependencyFiles = tcAcc.tcDependencyFiles
11301133
TopAttribs = tcAcc.topAttribs
11311134
TimeStamp = timestamp }
11321135

@@ -1163,21 +1166,22 @@ type IncrementalBuilder(frameworkTcImportsCache: FrameworkImportsCache, tcConfig
11631166
let flags = tcConfig.ComputeCanContainEntryPoint(sourceFiles |> List.map snd)
11641167
(sourceFiles,flags) ||> List.map2 (fun (m,nm) flag -> (m,nm,flag))
11651168

1169+
let defaultTimeStamp = DateTime.Now
11661170
let getFileTimeStamp (cache: TimeStampCache) fileName =
11671171
let ok, v = cache.Files.TryGetValue(fileName)
11681172
if ok then v else
11691173
let v =
11701174
if FileSystem.SafeExists(fileName) then
11711175
FileSystem.GetLastWriteTimeShim(fileName)
11721176
else
1173-
cache.Now
1177+
defaultTimeStamp
11741178
cache.Files.[fileName] <- v
11751179
v
11761180

11771181
let getProjectReferenceTimeStamp (cache: TimeStampCache) (pr: IProjectReference) =
11781182
let ok, v = cache.Projects.TryGetValue(pr)
11791183
if ok then v else
1180-
let v = defaultArg (pr.GetLogicalTimeStamp cache) cache.Now
1184+
let v = defaultArg (pr.GetLogicalTimeStamp cache) defaultTimeStamp
11811185
cache.Projects.[pr] <- v
11821186
v
11831187

@@ -1200,6 +1204,23 @@ type IncrementalBuilder(frameworkTcImportsCache: FrameworkImportsCache, tcConfig
12001204
for pr in projectReferences do
12011205
yield Choice2Of2 pr, (fun cache -> getProjectReferenceTimeStamp cache pr) ]
12021206

1207+
1208+
1209+
let basicDependencies =
1210+
[ for (UnresolvedAssemblyReference(referenceText, _)) in unresolvedReferences do
1211+
// Exclude things that are definitely not a file name
1212+
if not(FileSystem.IsInvalidPathShim(referenceText)) then
1213+
let file = if FileSystem.IsPathRootedShim(referenceText) then referenceText else Path.Combine(projectDirectory,referenceText)
1214+
yield file
1215+
1216+
for r in nonFrameworkResolutions do
1217+
yield r.resolvedPath ]
1218+
1219+
let allDependencies =
1220+
[ yield! basicDependencies
1221+
for (_,f,_) in sourceFiles do
1222+
yield f ]
1223+
12031224
// The IncrementalBuilder needs to hold up to one item that needs to be disposed, which is the tcImports for the incremental
12041225
// build.
12051226
let mutable cleanupItem = None: TcImports option
@@ -1303,6 +1324,7 @@ type IncrementalBuilder(frameworkTcImportsCache: FrameworkImportsCache, tcConfig
13031324
tcSymbolUses=[]
13041325
topAttribs=None
13051326
typedImplFiles=[]
1327+
tcDependencyFiles=basicDependencies
13061328
tcErrors=errorLogger.GetErrors() }
13071329
tcAcc
13081330

@@ -1342,7 +1364,8 @@ type IncrementalBuilder(frameworkTcImportsCache: FrameworkImportsCache, tcConfig
13421364
typedImplFiles=typedImplFiles
13431365
tcResolutions=tcAcc.tcResolutions @ [tcResolutions]
13441366
tcSymbolUses=tcAcc.tcSymbolUses @ [tcSymbolUses]
1345-
tcErrors = tcAcc.tcErrors @ parseErrors @ capturingErrorLogger.GetErrors() }
1367+
tcErrors = tcAcc.tcErrors @ parseErrors @ capturingErrorLogger.GetErrors()
1368+
tcDependencyFiles = filename :: tcAcc.tcDependencyFiles }
13461369
}
13471370

13481371
// Run part of the Eventually<_> computation until a timeout is reached. If not complete,
@@ -1509,20 +1532,6 @@ type IncrementalBuilder(frameworkTcImportsCache: FrameworkImportsCache, tcConfig
15091532
// END OF BUILD DESCRIPTION
15101533
// ---------------------------------------------------------------------------------------------
15111534

1512-
1513-
let fileDependencies =
1514-
[ for (UnresolvedAssemblyReference(referenceText, _)) in unresolvedReferences do
1515-
// Exclude things that are definitely not a file name
1516-
if not(FileSystem.IsInvalidPathShim(referenceText)) then
1517-
let file = if FileSystem.IsPathRootedShim(referenceText) then referenceText else Path.Combine(projectDirectory,referenceText)
1518-
yield file
1519-
1520-
for r in nonFrameworkResolutions do
1521-
yield r.resolvedPath
1522-
1523-
for (_,f,_) in sourceFiles do
1524-
yield f ]
1525-
15261535
let sourceFileInputs =
15271536
[ for (a,sourceFile,c) in sourceFiles do
15281537
yield a, sourceFile, c, (fun cache -> getFileTimeStamp cache sourceFile) ]
@@ -1562,7 +1571,7 @@ type IncrementalBuilder(frameworkTcImportsCache: FrameworkImportsCache, tcConfig
15621571
member __.FileChecked = fileChecked.Publish
15631572
member __.ProjectChecked = projectChecked.Publish
15641573
member __.ImportedCcusInvalidated = importsInvalidated.Publish
1565-
member __.Dependencies = fileDependencies
1574+
member __.AllDependenciesDeprecated = allDependencies
15661575

15671576
#if EXTENSIONTYPING
15681577
member __.ThereAreLiveTypeProviders =

src/fsharp/vs/IncrementalBuild.fsi

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ type internal PartialCheckResults =
7373
Errors : (PhasedError * FSharpErrorSeverity) list
7474
TcResolutions: TcResolutions list
7575
TcSymbolUses: TcSymbolUses list
76+
TcDependencyFiles: string list
7677
TopAttribs: TypeChecker.TopAttribs option
7778
TimeStamp: DateTime }
7879

@@ -112,7 +113,7 @@ type internal IncrementalBuilder =
112113
member ImportedCcusInvalidated : IEvent<string>
113114

114115
/// The list of files the build depends on
115-
member Dependencies : string list
116+
member AllDependenciesDeprecated : string list
116117
#if EXTENSIONTYPING
117118
/// Whether there are any 'live' type providers that may need a refresh when a project is Cleaned
118119
member ThereAreLiveTypeProviders : bool

0 commit comments

Comments
 (0)