Skip to content

Commit d966659

Browse files
brettfobaronfel
authored andcommitted
fire event when a value is bound at the root of a script evaluation (#7919)
* fire event when a value is bound at the root of a script evaluation * simplify event notifying bound values in interactive
1 parent 18b73fb commit d966659

File tree

4 files changed

+61
-1
lines changed

4 files changed

+61
-1
lines changed

src/fsharp/FSharp.Compiler.Private.Scripting/FSharpScript.fs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ type FSharpScript(?captureInput: bool, ?captureOutput: bool, ?additionalArgs: st
3939

4040
member __.AssemblyReferenceAdded = fsi.AssemblyReferenceAdded
4141

42+
member __.ValueBound = fsi.ValueBound
43+
4244
member __.ProvideInput = stdin.ProvideInput
4345

4446
member __.OutputProduced = outputProduced.Publish

src/fsharp/fsi/fsi.fs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -950,6 +950,7 @@ type internal FsiDynamicCompiler
950950
let assemblyName = "FSI-ASSEMBLY"
951951

952952
let assemblyReferenceAddedEvent = Control.Event<string>()
953+
let valueBoundEvent = Control.Event<_>()
953954

954955
let mutable fragmentId = 0
955956
let mutable prevIt : ValRef option = None
@@ -1155,6 +1156,10 @@ type internal FsiDynamicCompiler
11551156
if v.CompiledName = "it" then
11561157
itValue <- fsiValueOpt
11571158

1159+
match fsiValueOpt with
1160+
| Some fsiValue -> valueBoundEvent.Trigger(fsiValue.ReflectionValue, fsiValue.ReflectionType, v.CompiledName)
1161+
| None -> ()
1162+
11581163
let symbol = FSharpSymbol.Create(cenv, v.Item)
11591164
let symbolUse = FSharpSymbolUse(tcGlobals, newState.tcState.TcEnvFromImpls.DisplayEnv, symbol, ItemOccurence.Binding, v.DeclarationLocation)
11601165
fsi.TriggerEvaluation (fsiValueOpt, symbolUse, decl)
@@ -1331,6 +1336,8 @@ type internal FsiDynamicCompiler
13311336

13321337
member __.AssemblyReferenceAdded = assemblyReferenceAddedEvent.Publish
13331338

1339+
member __.ValueBound = valueBoundEvent.Publish
1340+
13341341
//----------------------------------------------------------------------------
13351342
// ctrl-c handling
13361343
//----------------------------------------------------------------------------
@@ -2222,7 +2229,6 @@ type internal FsiInteractionProcessor
22222229
let fsiInteractiveChecker = FsiInteractiveChecker(legacyReferenceResolver, checker, tcConfig, istate.tcGlobals, istate.tcImports, istate.tcState)
22232230
fsiInteractiveChecker.ParseAndCheckInteraction(ctok, SourceText.ofString text)
22242231

2225-
22262232
//----------------------------------------------------------------------------
22272233
// Server mode:
22282234
//----------------------------------------------------------------------------
@@ -2630,6 +2636,9 @@ type FsiEvaluationSession (fsi: FsiEvaluationSessionHostConfig, argv:string[], i
26302636

26312637
/// Event fires every time an assembly reference is added to the execution environment, e.g., via `#r`.
26322638
member __.AssemblyReferenceAdded = fsiDynamicCompiler.AssemblyReferenceAdded
2639+
2640+
/// Event fires when a root-level value is bound to an identifier, e.g., via `let x = ...`.
2641+
member __.ValueBound = fsiDynamicCompiler.ValueBound
26332642

26342643
/// Performs these steps:
26352644
/// - Load the dummy interaction, if any

src/fsharp/fsi/fsi.fsi

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,9 @@ type FsiEvaluationSession =
237237
/// Event fires every time an assembly reference is added to the execution environment, e.g., via `#r`.
238238
member AssemblyReferenceAdded : IEvent<string>
239239

240+
/// Event fires when a root-level value is bound to an identifier, e.g., via `let x = ...`.
241+
member ValueBound : IEvent<obj * System.Type * string>
242+
240243
/// Load the dummy interaction, load the initial files, and,
241244
/// if interacting, start the background thread to read the standard input.
242245
///

tests/FSharp.Compiler.Private.Scripting.UnitTests/FSharpScriptTests.fs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,3 +112,49 @@ type InteractiveTests() =
112112
Assert.True(wasCancelled)
113113
Assert.LessOrEqual(sw.ElapsedMilliseconds, sleepTime)
114114
Assert.AreEqual(None, result)
115+
116+
[<Test>]
117+
member _.``Values bound at the root trigger an event``() =
118+
let mutable foundX = false
119+
let mutable foundY = false
120+
let mutable foundCount = 0
121+
use script = new FSharpScript()
122+
script.ValueBound
123+
|> Event.add (fun (value, typ, name) ->
124+
foundX <- foundX || (name = "x" && typ = typeof<int> && value :?> int = 1)
125+
foundY <- foundY || (name = "y" && typ = typeof<int> && value :?> int = 2)
126+
foundCount <- foundCount + 1)
127+
let code = @"
128+
let x = 1
129+
let y = 2
130+
"
131+
script.Eval(code) |> ignoreValue
132+
Assert.True(foundX)
133+
Assert.True(foundY)
134+
Assert.AreEqual(2, foundCount)
135+
136+
[<Test>]
137+
member _.``Values re-bound trigger an event``() =
138+
let mutable foundXCount = 0
139+
use script = new FSharpScript()
140+
script.ValueBound
141+
|> Event.add (fun (_value, typ, name) ->
142+
if name = "x" && typ = typeof<int> then foundXCount <- foundXCount + 1)
143+
script.Eval("let x = 1") |> ignoreValue
144+
script.Eval("let x = 2") |> ignoreValue
145+
Assert.AreEqual(2, foundXCount)
146+
147+
[<Test>]
148+
member _.``Nested let bindings don't trigger event``() =
149+
let mutable foundInner = false
150+
use script = new FSharpScript()
151+
script.ValueBound
152+
|> Event.add (fun (_value, _typ, name) ->
153+
foundInner <- foundInner || name = "inner")
154+
let code = @"
155+
let x =
156+
let inner = 1
157+
()
158+
"
159+
script.Eval(code) |> ignoreValue
160+
Assert.False(foundInner)

0 commit comments

Comments
 (0)