Skip to content

Commit

Permalink
fire event when a value is bound at the root of a script evaluation (#…
Browse files Browse the repository at this point in the history
…7919)

* fire event when a value is bound at the root of a script evaluation

* simplify event notifying bound values in interactive
  • Loading branch information
brettfo authored Dec 3, 2019
1 parent b757c16 commit 4978145
Show file tree
Hide file tree
Showing 4 changed files with 61 additions and 1 deletion.
2 changes: 2 additions & 0 deletions src/fsharp/FSharp.Compiler.Private.Scripting/FSharpScript.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
11 changes: 10 additions & 1 deletion src/fsharp/fsi/fsi.fs
Original file line number Diff line number Diff line change
Expand Up @@ -950,6 +950,7 @@ type internal FsiDynamicCompiler
let assemblyName = "FSI-ASSEMBLY"

let assemblyReferenceAddedEvent = Control.Event<string>()
let valueBoundEvent = Control.Event<_>()

let mutable fragmentId = 0
let mutable prevIt : ValRef option = None
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -1331,6 +1336,8 @@ type internal FsiDynamicCompiler

member __.AssemblyReferenceAdded = assemblyReferenceAddedEvent.Publish

member __.ValueBound = valueBoundEvent.Publish

//----------------------------------------------------------------------------
// ctrl-c handling
//----------------------------------------------------------------------------
Expand Down Expand Up @@ -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:
//----------------------------------------------------------------------------
Expand Down Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions src/fsharp/fsi/fsi.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -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<string>

/// Event fires when a root-level value is bound to an identifier, e.g., via `let x = ...`.
member ValueBound : IEvent<obj * System.Type * string>

/// Load the dummy interaction, load the initial files, and,
/// if interacting, start the background thread to read the standard input.
///
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,3 +122,49 @@ type InteractiveTests() =
Assert.True(wasCancelled)
Assert.LessOrEqual(sw.ElapsedMilliseconds, sleepTime)
Assert.AreEqual(None, result)

[<Test>]
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<int> && value :?> int = 1)
foundY <- foundY || (name = "y" && typ = typeof<int> && 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)

[<Test>]
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<int> then foundXCount <- foundXCount + 1)
script.Eval("let x = 1") |> ignoreValue
script.Eval("let x = 2") |> ignoreValue
Assert.AreEqual(2, foundXCount)

[<Test>]
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)

0 comments on commit 4978145

Please sign in to comment.