Skip to content

Commit 4978145

Browse files
authored
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 b757c16 commit 4978145

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
@@ -38,6 +38,8 @@ type FSharpScript(?captureInput: bool, ?captureOutput: bool, ?additionalArgs: st
3838

3939
member __.AssemblyReferenceAdded = fsi.AssemblyReferenceAdded
4040

41+
member __.ValueBound = fsi.ValueBound
42+
4143
member __.ProvideInput = stdin.ProvideInput
4244

4345
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
@@ -122,3 +122,49 @@ type InteractiveTests() =
122122
Assert.True(wasCancelled)
123123
Assert.LessOrEqual(sw.ElapsedMilliseconds, sleepTime)
124124
Assert.AreEqual(None, result)
125+
126+
[<Test>]
127+
member _.``Values bound at the root trigger an event``() =
128+
let mutable foundX = false
129+
let mutable foundY = false
130+
let mutable foundCount = 0
131+
use script = new FSharpScript()
132+
script.ValueBound
133+
|> Event.add (fun (value, typ, name) ->
134+
foundX <- foundX || (name = "x" && typ = typeof<int> && value :?> int = 1)
135+
foundY <- foundY || (name = "y" && typ = typeof<int> && value :?> int = 2)
136+
foundCount <- foundCount + 1)
137+
let code = @"
138+
let x = 1
139+
let y = 2
140+
"
141+
script.Eval(code) |> ignoreValue
142+
Assert.True(foundX)
143+
Assert.True(foundY)
144+
Assert.AreEqual(2, foundCount)
145+
146+
[<Test>]
147+
member _.``Values re-bound trigger an event``() =
148+
let mutable foundXCount = 0
149+
use script = new FSharpScript()
150+
script.ValueBound
151+
|> Event.add (fun (_value, typ, name) ->
152+
if name = "x" && typ = typeof<int> then foundXCount <- foundXCount + 1)
153+
script.Eval("let x = 1") |> ignoreValue
154+
script.Eval("let x = 2") |> ignoreValue
155+
Assert.AreEqual(2, foundXCount)
156+
157+
[<Test>]
158+
member _.``Nested let bindings don't trigger event``() =
159+
let mutable foundInner = false
160+
use script = new FSharpScript()
161+
script.ValueBound
162+
|> Event.add (fun (_value, _typ, name) ->
163+
foundInner <- foundInner || name = "inner")
164+
let code = @"
165+
let x =
166+
let inner = 1
167+
()
168+
"
169+
script.Eval(code) |> ignoreValue
170+
Assert.False(foundInner)

0 commit comments

Comments
 (0)