diff --git a/src/fsharp/FSharp.Compiler.Private.Scripting/FSharpScript.fs b/src/fsharp/FSharp.Compiler.Private.Scripting/FSharpScript.fs index 6380c203eda..3d9e113bcc5 100644 --- a/src/fsharp/FSharp.Compiler.Private.Scripting/FSharpScript.fs +++ b/src/fsharp/FSharp.Compiler.Private.Scripting/FSharpScript.fs @@ -38,6 +38,8 @@ type FSharpScript(?captureInput: bool, ?captureOutput: bool, ?additionalArgs: st member __.AssemblyReferenceAdded = fsi.AssemblyReferenceAdded + member __.ValueBound = fsi.ValueBound + member __.ProvideInput = stdin.ProvideInput member __.OutputProduced = outputProduced.Publish diff --git a/src/fsharp/fsi/fsi.fs b/src/fsharp/fsi/fsi.fs index 78b142bd516..c5e71384f1f 100644 --- a/src/fsharp/fsi/fsi.fs +++ b/src/fsharp/fsi/fsi.fs @@ -950,6 +950,7 @@ type internal FsiDynamicCompiler let assemblyName = "FSI-ASSEMBLY" let assemblyReferenceAddedEvent = Control.Event() + let valueBoundEvent = Control.Event<_>() let mutable fragmentId = 0 let mutable prevIt : ValRef option = None @@ -1155,6 +1156,10 @@ type internal FsiDynamicCompiler if v.CompiledName = "it" then itValue <- fsiValueOpt + match fsiValueOpt with + | Some fsiValue -> valueBoundEvent.Trigger(fsiValue.ReflectionValue, fsiValue.ReflectionType, v.CompiledName) + | None -> () + let symbol = FSharpSymbol.Create(cenv, v.Item) let symbolUse = FSharpSymbolUse(tcGlobals, newState.tcState.TcEnvFromImpls.DisplayEnv, symbol, ItemOccurence.Binding, v.DeclarationLocation) fsi.TriggerEvaluation (fsiValueOpt, symbolUse, decl) @@ -1331,6 +1336,8 @@ type internal FsiDynamicCompiler member __.AssemblyReferenceAdded = assemblyReferenceAddedEvent.Publish + member __.ValueBound = valueBoundEvent.Publish + //---------------------------------------------------------------------------- // ctrl-c handling //---------------------------------------------------------------------------- @@ -2222,7 +2229,6 @@ type internal FsiInteractionProcessor let fsiInteractiveChecker = FsiInteractiveChecker(legacyReferenceResolver, checker, tcConfig, istate.tcGlobals, istate.tcImports, istate.tcState) fsiInteractiveChecker.ParseAndCheckInteraction(ctok, SourceText.ofString text) - //---------------------------------------------------------------------------- // Server mode: //---------------------------------------------------------------------------- @@ -2630,6 +2636,9 @@ type FsiEvaluationSession (fsi: FsiEvaluationSessionHostConfig, argv:string[], i /// Event fires every time an assembly reference is added to the execution environment, e.g., via `#r`. member __.AssemblyReferenceAdded = fsiDynamicCompiler.AssemblyReferenceAdded + + /// Event fires when a root-level value is bound to an identifier, e.g., via `let x = ...`. + member __.ValueBound = fsiDynamicCompiler.ValueBound /// Performs these steps: /// - Load the dummy interaction, if any diff --git a/src/fsharp/fsi/fsi.fsi b/src/fsharp/fsi/fsi.fsi index bed1b74e61a..c576a33fbdc 100644 --- a/src/fsharp/fsi/fsi.fsi +++ b/src/fsharp/fsi/fsi.fsi @@ -237,6 +237,9 @@ type FsiEvaluationSession = /// Event fires every time an assembly reference is added to the execution environment, e.g., via `#r`. member AssemblyReferenceAdded : IEvent + /// Event fires when a root-level value is bound to an identifier, e.g., via `let x = ...`. + member ValueBound : IEvent + /// Load the dummy interaction, load the initial files, and, /// if interacting, start the background thread to read the standard input. /// diff --git a/tests/FSharp.Compiler.Private.Scripting.UnitTests/FSharpScriptTests.fs b/tests/FSharp.Compiler.Private.Scripting.UnitTests/FSharpScriptTests.fs index 9b4707b7872..70b6b2662e8 100644 --- a/tests/FSharp.Compiler.Private.Scripting.UnitTests/FSharpScriptTests.fs +++ b/tests/FSharp.Compiler.Private.Scripting.UnitTests/FSharpScriptTests.fs @@ -122,3 +122,49 @@ type InteractiveTests() = Assert.True(wasCancelled) Assert.LessOrEqual(sw.ElapsedMilliseconds, sleepTime) Assert.AreEqual(None, result) + + [] + member _.``Values bound at the root trigger an event``() = + let mutable foundX = false + let mutable foundY = false + let mutable foundCount = 0 + use script = new FSharpScript() + script.ValueBound + |> Event.add (fun (value, typ, name) -> + foundX <- foundX || (name = "x" && typ = typeof && value :?> int = 1) + foundY <- foundY || (name = "y" && typ = typeof && value :?> int = 2) + foundCount <- foundCount + 1) + let code = @" +let x = 1 +let y = 2 +" + script.Eval(code) |> ignoreValue + Assert.True(foundX) + Assert.True(foundY) + Assert.AreEqual(2, foundCount) + + [] + member _.``Values re-bound trigger an event``() = + let mutable foundXCount = 0 + use script = new FSharpScript() + script.ValueBound + |> Event.add (fun (_value, typ, name) -> + if name = "x" && typ = typeof then foundXCount <- foundXCount + 1) + script.Eval("let x = 1") |> ignoreValue + script.Eval("let x = 2") |> ignoreValue + Assert.AreEqual(2, foundXCount) + + [] + member _.``Nested let bindings don't trigger event``() = + let mutable foundInner = false + use script = new FSharpScript() + script.ValueBound + |> Event.add (fun (_value, _typ, name) -> + foundInner <- foundInner || name = "inner") + let code = @" +let x = + let inner = 1 + () +" + script.Eval(code) |> ignoreValue + Assert.False(foundInner)