Skip to content

Commit ed061e6

Browse files
Only reload projects that need and populate test runner (#1082)
Co-authored-by: Chet Husk <[email protected]>
1 parent 39427d4 commit ed061e6

File tree

6 files changed

+402
-186
lines changed

6 files changed

+402
-186
lines changed
Lines changed: 290 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,290 @@
1+
namespace FsAutoComplete.Adaptive
2+
3+
open System
4+
open FSharp.Data.Adaptive
5+
open FSharp.Data.Traceable
6+
7+
[<AutoOpen>]
8+
module AdaptiveExtensions =
9+
type ChangeableHashMap<'Key, 'Value> with
10+
11+
/// <summary>
12+
/// Adds the given key and calls the adder function if no previous key exists.
13+
/// Otherwise calls updater with the current key/value and returns a new value to be set.
14+
/// Returns true when the map changed.
15+
/// </summary>
16+
member x.AddOrUpdate(key, adder, updater) =
17+
match x.TryGetValue key with
18+
| None -> x.Add(key, adder key)
19+
| Some v -> x.Add(key, updater key v)
20+
21+
/// <summary>
22+
/// Adds the given key and calls the adder function if no previous key exists.
23+
/// Otherwise calls updater with the current key/value but does not override existing value in the map.
24+
/// This is useful when the 'Value is itself a changeable value like a cval, aset, amap which should be changed
25+
/// but the parent container doesn't need to know about those changes itself.
26+
/// </summary>
27+
member x.AddOrElse(key, adder, updater) =
28+
match x.TryGetValue key with
29+
| None -> x.Add(key, adder key) |> ignore
30+
| Some v -> updater key v
31+
32+
33+
module Utils =
34+
let cheapEqual (a: 'T) (b: 'T) =
35+
ShallowEqualityComparer<'T>.Instance.Equals(a, b)
36+
37+
/// <summary>
38+
/// Maps and calls dispose before mapping of new values. Useful for cleaning up callbacks like AddMarkingCallback for tracing purposes.
39+
/// </summary>
40+
type MapDisposableTupleVal<'T1, 'T2, 'Disposable when 'Disposable :> IDisposable>
41+
(mapping: 'T1 -> ('T2 * 'Disposable), input: aval<'T1>) =
42+
inherit AVal.AbstractVal<'T2>()
43+
44+
let mutable cache: ValueOption<struct ('T1 * 'T2 * 'Disposable)> = ValueNone
45+
46+
override x.Compute(token: AdaptiveToken) =
47+
let i = input.GetValue token
48+
49+
match cache with
50+
| ValueSome(struct (a, b, _)) when Utils.cheapEqual a i -> b
51+
| ValueSome(struct (a, b, c)) ->
52+
(c :> IDisposable).Dispose()
53+
let (b, c) = mapping i
54+
cache <- ValueSome(struct (i, b, c))
55+
b
56+
| ValueNone ->
57+
let (b, c) = mapping i
58+
cache <- ValueSome(struct (i, b, c))
59+
b
60+
61+
module AVal =
62+
let mapOption f = AVal.map (Option.map f)
63+
64+
/// <summary>
65+
/// Maps and calls dispose before mapping of new values. Useful for cleaning up callbacks like AddMarkingCallback for tracing purposes.
66+
/// </summary>
67+
let mapDisposableTuple mapper value =
68+
MapDisposableTupleVal(mapper, value) :> aval<_>
69+
70+
/// <summary>
71+
/// Calls a mapping function which creates additional dependencies to be tracked.
72+
/// </summary>
73+
let mapWithAdditionalDependenies (mapping: 'a -> 'b * #seq<#IAdaptiveValue>) (value: aval<'a>) : aval<'b> =
74+
let mutable lastDeps = HashSet.empty
75+
76+
{ new AVal.AbstractVal<'b>() with
77+
member x.Compute(token: AdaptiveToken) =
78+
let input = value.GetValue token
79+
80+
// re-evaluate the mapping based on the (possibly new input)
81+
let result, deps = mapping input
82+
83+
// compute the change in the additional dependencies and adjust the graph accordingly
84+
let newDeps = HashSet.ofSeq deps
85+
86+
for op in HashSet.computeDelta lastDeps newDeps do
87+
match op with
88+
| Add(_, d) ->
89+
// the new dependency needs to be evaluated with our token, s.t. we depend on it in the future
90+
d.GetValueUntyped token |> ignore
91+
| Rem(_, d) ->
92+
// we no longer need to depend on the old dependency so we can remove ourselves from its outputs
93+
lock d.Outputs (fun () -> d.Outputs.Remove x) |> ignore
94+
95+
lastDeps <- newDeps
96+
97+
result }
98+
:> aval<_>
99+
100+
/// <summary>
101+
/// Creates observables from adaptive values
102+
/// </summary>
103+
module Observable =
104+
open System.Reactive.Linq
105+
106+
/// <summary>
107+
/// Creates an observable with the given object and will be executed whenever the object gets marked out-of-date. Note that it does not trigger when the object is currently out-of-date.
108+
/// </summary>
109+
/// <param name="aval">The aval to get out-of-date information from.</param>
110+
/// <returns>An observable</returns>
111+
let onWeakMarking (aval: #aval<_>) =
112+
Observable.Create(fun (obs: IObserver<unit>) -> aval.AddWeakMarkingCallback(obs.OnNext))
113+
114+
module ASet =
115+
/// Creates an amap with the keys from the set and the values given by mapping and
116+
/// adaptively applies the given mapping function to all elements and returns a new amap containing the results.
117+
let mapAtoAMap mapper src =
118+
src |> ASet.mapToAMap mapper |> AMap.mapA (fun _ v -> v)
119+
120+
module AMap =
121+
open FSharp.Data.Traceable
122+
123+
/// A simple multi-map implementation.
124+
type internal MultiSetMap<'k, 'v> = HashMap<'k, HashSet<'v>>
125+
126+
/// A simple multi-map implementation.
127+
module internal MultiSetMap =
128+
[<GeneralizableValue>]
129+
let empty<'k, 'v> : MultiSetMap<'k, 'v> = HashMap.empty
130+
131+
let add (key: 'k) (value: 'v) (m: MultiSetMap<'k, 'v>) : MultiSetMap<'k, 'v> =
132+
m
133+
|> HashMap.alter key (fun old ->
134+
match old with
135+
| Some old -> Some(HashSet.add value old)
136+
| None -> Some(HashSet.single value))
137+
138+
let remove (key: 'k) (value: 'v) (m: MultiSetMap<'k, 'v>) : bool * MultiSetMap<'k, 'v> =
139+
let wasLast = ref false
140+
141+
let result =
142+
m
143+
|> HashMap.alter key (fun old ->
144+
match old with
145+
| None -> None
146+
| Some old ->
147+
let s = HashSet.remove value old
148+
149+
if HashSet.isEmpty s then
150+
wasLast.Value <- true
151+
None
152+
else
153+
Some s)
154+
155+
wasLast.Value, result
156+
157+
let find (key: 'k) (m: MultiSetMap<'k, 'v>) =
158+
match HashMap.tryFind key m with
159+
| Some s -> s
160+
| None -> HashSet.empty
161+
162+
163+
/// Reader for batchRecalc operations.
164+
[<Sealed>]
165+
type BatchRecalculateDirty<'k, 'a, 'b>(input: amap<'k, 'a>, mapping: HashMap<'k, 'a> -> HashMap<'k, aval<'b>>) =
166+
inherit AbstractReader<HashMapDelta<'k, 'b>>(HashMapDelta.empty)
167+
168+
let reader = input.GetReader()
169+
do reader.Tag <- "input"
170+
let cacheLock = obj ()
171+
let mutable cache: HashMap<'k, aval<'b>> = HashMap.Empty
172+
let mutable targets = MultiSetMap.empty<aval<'b>, 'k>
173+
let mutable dirty = HashMap.empty<'k, aval<'b>>
174+
175+
let consumeDirty () =
176+
lock cacheLock (fun () ->
177+
let d = dirty
178+
dirty <- HashMap.empty
179+
d)
180+
181+
override x.InputChangedObject(t, o) =
182+
#if FABLE_COMPILER
183+
if isNull o.Tag then
184+
let o = unbox<aval<'b>> o
185+
186+
for i in MultiSetMap.find o targets do
187+
dirty <- HashMap.add i o dirty
188+
#else
189+
match o with
190+
| :? aval<'b> as o ->
191+
lock cacheLock (fun () ->
192+
for i in MultiSetMap.find o targets do
193+
dirty <- HashMap.add i o dirty
194+
195+
)
196+
| _ -> ()
197+
#endif
198+
199+
override x.Compute t =
200+
let mutable dirty = consumeDirty ()
201+
let old = reader.State
202+
let ops = reader.GetChanges t |> HashMapDelta.toHashMap
203+
204+
let setOps, removeOps =
205+
((HashMap.empty, HashMap.empty), ops)
206+
||> HashMap.fold (fun (sets, rems) i op ->
207+
dirty <- HashMap.remove i dirty
208+
209+
cache <-
210+
match HashMap.tryRemove i cache with
211+
| Some(o, remaingCache) ->
212+
let rem, rest = MultiSetMap.remove o i targets
213+
targets <- rest
214+
215+
if rem then
216+
o.Outputs.Remove x |> ignore
217+
218+
remaingCache
219+
| None -> cache
220+
221+
match op with
222+
| Set v -> HashMap.add i v sets, rems
223+
| Remove -> sets, HashMap.add i Remove rems)
224+
225+
226+
let mutable changes = HashMap.empty
227+
228+
let setOps =
229+
(setOps, dirty)
230+
||> HashMap.fold (fun s k _ ->
231+
match HashMap.tryFind k old with
232+
| Some v -> HashMap.add k v s
233+
| None -> s)
234+
235+
for i, k in mapping setOps do
236+
cache <- HashMap.add i k cache
237+
let v = k.GetValue t
238+
targets <- MultiSetMap.add k i targets
239+
changes <- HashMap.add i (Set v) changes
240+
241+
HashMap.union removeOps changes |> HashMapDelta
242+
243+
244+
/// Adaptively looks up the given key in the map and flattens the value to be easily worked with. Note that this operation should not be used extensively since its resulting aval will be re-evaluated upon every change of the map.
245+
let tryFindAndFlatten (key: 'Key) (map: amap<'Key, aval<option<'Value>>>) =
246+
aval {
247+
match! AMap.tryFind key map with
248+
| Some x -> return! x
249+
| None -> return None
250+
}
251+
252+
/// Adaptively looks up the given key in the map and binds the value to be easily worked with. Note that this operation should not be used extensively since its resulting aval will be re-evaluated upon every change of the map.
253+
let tryFindA (key: 'Key) (map: amap<'Key, #aval<'Value>>) =
254+
aval {
255+
match! AMap.tryFind key map with
256+
| Some v ->
257+
let! v2 = v
258+
return Some v2
259+
| None -> return None
260+
}
261+
262+
/// Adaptively applies the given mapping function to all elements and returns a new amap containing the results.
263+
let mapAVal
264+
(mapper: 'Key -> 'InValue -> aval<'OutValue>)
265+
(map: #amap<'Key, #aval<'InValue>>)
266+
: amap<'Key, aval<'OutValue>> =
267+
map |> AMap.map (fun k v -> AVal.bind (mapper k) v)
268+
269+
270+
/// Adaptively applies the given mapping to all changes and reapplies mapping on dirty outputs
271+
let batchRecalcDirty (mapping: HashMap<'K, 'T1> -> HashMap<'K, aval<'T2>>) (map: amap<'K, 'T1>) =
272+
// if map.IsConstant then
273+
// let map = force map |> mapping
274+
// if map |> HashMap.forall (fun _ v -> v.IsConstant) then
275+
// constant (fun () -> map |> HashMap.map (fun _ v -> AVal.force v))
276+
// else
277+
// // TODO better impl possible
278+
// create (fun () -> BatchRecalculateDirty(ofHashMap map, id))
279+
// else
280+
AMap.ofReader (fun () -> BatchRecalculateDirty(map, mapping))
281+
282+
let mapWithAdditionalDependenies
283+
(mapping: HashMap<'K, 'T1> -> HashMap<'K, 'T2 * #seq<#IAdaptiveValue>>)
284+
(map: amap<'K, 'T1>)
285+
=
286+
let mapping =
287+
mapping
288+
>> HashMap.map (fun _ v -> AVal.constant v |> AVal.mapWithAdditionalDependenies (id))
289+
290+
batchRecalcDirty mapping map

src/FsAutoComplete.Core/CompilerServiceInterface.fs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -253,10 +253,12 @@ type FSharpCompilerServiceChecker(hasAnalyzers, typecheckCacheSize) =
253253
checker.ClearLanguageServiceRootCachesAndCollectAndFinalizeAllTransients()
254254

255255
member __.ParseFile(fn: string<LocalPath>, source, fpo) =
256-
checkerLogger.info (Log.setMessage "ParseFile - {file}" >> Log.addContextDestructured "file" fn)
256+
async {
257+
checkerLogger.info (Log.setMessage "ParseFile - {file}" >> Log.addContextDestructured "file" fn)
257258

258-
let path = UMX.untag fn
259-
checker.ParseFile(path, source, fpo)
259+
let path = UMX.untag fn
260+
return! checker.ParseFile(path, source, fpo)
261+
}
260262

261263
/// <summary>Parse and check a source code file, returning a handle to the results</summary>
262264
/// <param name="filePath">The name of the file in the project whose source is being checked.</param>

src/FsAutoComplete.Core/FsAutoComplete.Core.fsproj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
<TrimmerRootAssembly Include="FsAutoComplete.Core" />
1010
</ItemGroup>
1111
<ItemGroup>
12+
<Compile Include="AdaptiveExtensions.fs" />
1213
<Compile Include="Consts.fs" />
1314
<Compile Include="Debug.fs" />
1415
<Compile Include="Utils.fs" />
@@ -45,4 +46,4 @@
4546
<Compile Include="Commands.fs" />
4647
</ItemGroup>
4748
<Import Project="..\..\.paket\Paket.Restore.targets" />
48-
</Project>
49+
</Project>

src/FsAutoComplete.Core/paket.references

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ System.Configuration.ConfigurationManager
77
FSharp.UMX
88
FsToolkit.ErrorHandling
99
Fantomas.Client
10+
FSharp.Data.Adaptive
1011

1112
Ionide.ProjInfo.ProjectSystem
1213
System.Reflection.Metadata

0 commit comments

Comments
 (0)