Skip to content

Commit

Permalink
Merge pull request #9077 from dotnet/merges/master-to-feature/witness…
Browse files Browse the repository at this point in the history
…-passing

Merge master to feature/witness-passing
  • Loading branch information
KevinRansom authored Apr 30, 2020
2 parents b969432 + 8b7934f commit 640d3a0
Show file tree
Hide file tree
Showing 4 changed files with 308 additions and 6 deletions.
54 changes: 48 additions & 6 deletions src/fsharp/fsi/fsi.fs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ type FsiValue(reflectionValue:obj, reflectionType:Type, fsharpType:FSharpType) =
member x.ReflectionType = reflectionType
member x.FSharpType = fsharpType

[<Sealed>]
type FsiBoundValue(name: string, value: FsiValue) =
member _.Name = name
member _.Value = value

[<AutoOpen>]
module internal Utilities =
Expand Down Expand Up @@ -927,6 +931,7 @@ type internal FsiDynamicCompilerState =
tcState : TcState
tcImports : TcImports
ilxGenerator : IlxGen.IlxAssemblyGenerator
boundValues : NameMap<Val>
// Why is this not in FsiOptions?
timing : bool
debugBreak : bool }
Expand Down Expand Up @@ -1114,6 +1119,13 @@ type internal FsiDynamicCompiler
// Return the new state and the environment at the end of the last input, ready for further inputs.
(istate,tcEnvAtEndOfLastInput,declaredImpls)

let tryGetGeneratedValue istate cenv v =
match istate.ilxGenerator.LookupGeneratedValue(valuePrinter.GetEvaluationContext(istate.emEnv), v) with
| Some (res, ty) ->
Some (FsiValue(res, ty, FSharpType(cenv, v.Type)))
| _ ->
None

let nextFragmentId() = fragmentId <- fragmentId + 1; fragmentId

let mkFragmentPath i =
Expand Down Expand Up @@ -1146,6 +1158,7 @@ type internal FsiDynamicCompiler

// Find all new declarations the EvaluationListener
let mutable itValue = None
let mutable boundValues = newState.boundValues
try
let contents = FSharpAssemblyContents(tcGlobals, tcState.Ccu, Some tcState.CcuSig, tcImports, declaredImpls)
let contentFile = contents.ImplementationFiles.[0]
Expand All @@ -1161,11 +1174,11 @@ type internal FsiDynamicCompiler
if v.IsModuleValueOrMember && not v.IsMember then
let fsiValueOpt =
match v.Item with
| Item.Value vref ->
let optValue = newState.ilxGenerator.LookupGeneratedValue(valuePrinter.GetEvaluationContext(newState.emEnv), vref.Deref)
match optValue with
| Some (res, ty) -> Some(FsiValue(res, ty, FSharpType(cenv, vref.Type)))
| None -> None
| Item.Value vref ->
let fsiValueOpt = tryGetGeneratedValue newState cenv vref.Deref
if fsiValueOpt.IsSome then
boundValues <- boundValues |> NameMap.add v.CompiledName vref.Deref
fsiValueOpt
| _ -> None

if v.CompiledName = "it" then
Expand All @@ -1191,7 +1204,7 @@ type internal FsiDynamicCompiler
| _ -> ()
with _ -> ()

newState, Completed itValue
{ newState with boundValues = boundValues }, Completed itValue

/// Evaluate the given expression and produce a new interactive state.
member fsiDynamicCompiler.EvalParsedExpression (ctok, errorLogger: ErrorLogger, istate, expr: SynExpr) =
Expand Down Expand Up @@ -1382,6 +1395,28 @@ type internal FsiDynamicCompiler
let istate = (istate, sourceFiles, inputs) |||> List.fold2 (fun istate sourceFile input -> fsiDynamicCompiler.ProcessMetaCommandsFromInputAsInteractiveCommands(ctok, istate, sourceFile, input))
fsiDynamicCompiler.EvalParsedSourceFiles (ctok, errorLogger, istate, inputs)

member __.GetBoundValues istate =
let cenv = SymbolEnv(istate.tcGlobals, istate.tcState.Ccu, Some istate.tcState.CcuSig, istate.tcImports)
[ for pair in istate.boundValues do
let nm = pair.Key
let v = pair.Value
match tryGetGeneratedValue istate cenv v with
| Some fsiValue ->
yield FsiBoundValue(nm, fsiValue)
| _ ->
() ]

member __.TryFindBoundValue(istate, nm) =
match istate.boundValues.TryFind nm with
| Some v ->
let cenv = SymbolEnv(istate.tcGlobals, istate.tcState.Ccu, Some istate.tcState.CcuSig, istate.tcImports)
match tryGetGeneratedValue istate cenv v with
| Some fsiValue ->
Some (FsiBoundValue(nm, fsiValue))
| _ ->
None
| _ ->
None

member __.GetInitialInteractiveState () =
let tcConfig = TcConfig.Create(tcConfigB,validate=false)
Expand All @@ -1399,6 +1434,7 @@ type internal FsiDynamicCompiler
tcState = tcState
tcImports = tcImports
ilxGenerator = ilxGenerator
boundValues = NameMap.empty
timing = false
debugBreak = false
}
Expand Down Expand Up @@ -2753,6 +2789,12 @@ type FsiEvaluationSession (fsi: FsiEvaluationSessionHostConfig, argv:string[], i
/// Event fires when a root-level value is bound to an identifier, e.g., via `let x = ...`.
member __.ValueBound = fsiDynamicCompiler.ValueBound

member __.GetBoundValues() =
fsiDynamicCompiler.GetBoundValues fsiInteractionProcessor.CurrentState

member __.TryFindBoundValue(name: string) =
fsiDynamicCompiler.TryFindBoundValue(fsiInteractionProcessor.CurrentState, name)

/// Performs these steps:
/// - Load the dummy interaction, if any
/// - Set up exception handling, if any
Expand Down
16 changes: 16 additions & 0 deletions src/fsharp/fsi/fsi.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,16 @@ type FsiValue =
member FSharpType : FSharpType
#endif

[<Sealed>]
/// Represents an evaluated F# value that is bound to an identifier
type FsiBoundValue =

/// The identifier of the value
member Name : string

/// The evaluated F# value
member Value : FsiValue

[<Class>]
type EvaluationEventArgs =
inherit System.EventArgs
Expand Down Expand Up @@ -250,6 +260,12 @@ type FsiEvaluationSession =
/// Event fires when a root-level value is bound to an identifier, e.g., via `let x = ...`.
member ValueBound : IEvent<obj * System.Type * string>

/// Gets the root-level values that are bound to an identifier
member GetBoundValues : unit -> FsiBoundValue list

/// Tries to find a root-level value that is bound to the given identifier
member TryFindBoundValue : name: string -> FsiBoundValue option

/// 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 @@ -18,6 +18,7 @@
<Compile Include="ProductVersion.fs" />
<Compile Include="EditDistance.fs" />
<Compile Include="SuggestionBuffer.fs" />
<Compile Include="FsiTests.fs" />
</ItemGroup>

<ItemGroup>
Expand Down
243 changes: 243 additions & 0 deletions tests/FSharp.Compiler.UnitTests/FsiTests.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
module FSharp.Compiler.UnitTests.FsiTests

open System.IO
open FSharp.Compiler.Interactive.Shell
open NUnit.Framework

let createFsiSession () =
// Intialize output and input streams
let inStream = new StringReader("")
let outStream = new CompilerOutputStream()
let errStream = new CompilerOutputStream()

// Build command line arguments & start FSI session
let argv = [| "C:\\fsi.exe" |]
let allArgs = Array.append argv [|"--noninteractive"|]

let fsiConfig = FsiEvaluationSession.GetDefaultConfiguration()
FsiEvaluationSession.Create(fsiConfig, allArgs, inStream, new StreamWriter(outStream), new StreamWriter(errStream), collectible = true)

[<Test>]
let ``No bound values at the start of FSI session`` () =
use fsiSession = createFsiSession ()
let values = fsiSession.GetBoundValues()
Assert.IsEmpty values

[<Test>]
let ``Bound value has correct name`` () =
use fsiSession = createFsiSession ()

fsiSession.EvalInteraction("let x = 1")

let boundValue = fsiSession.GetBoundValues() |> List.exactlyOne

Assert.AreEqual("x", boundValue.Name)

[<Test>]
let ``Bound value has correct value`` () =
use fsiSession = createFsiSession ()

fsiSession.EvalInteraction("let y = 2")

let boundValue = fsiSession.GetBoundValues() |> List.exactlyOne

Assert.AreEqual(2, boundValue.Value.ReflectionValue)

[<Test>]
let ``Bound value has correct type`` () =
use fsiSession = createFsiSession ()

fsiSession.EvalInteraction("let z = 3")

let boundValue = fsiSession.GetBoundValues() |> List.exactlyOne

Assert.AreEqual(typeof<int>, boundValue.Value.ReflectionType)

[<Test>]
let ``Seven bound values are ordered and have their correct name`` () =
use fsiSession = createFsiSession ()

fsiSession.EvalInteraction("let x = 1")
fsiSession.EvalInteraction("let y = 2")
fsiSession.EvalInteraction("let z = 3")
fsiSession.EvalInteraction("let a = 4")
fsiSession.EvalInteraction("let ccc = 5")
fsiSession.EvalInteraction("let b = 6")
fsiSession.EvalInteraction("let aa = 7")

let names = fsiSession.GetBoundValues() |> List.map (fun x -> x.Name)

Assert.AreEqual(["a";"aa";"b";"ccc";"x";"y";"z"], names)

[<Test>]
let ``Seven bound values are ordered and have their correct value`` () =
use fsiSession = createFsiSession ()

fsiSession.EvalInteraction("let x = 1")
fsiSession.EvalInteraction("let y = 2")
fsiSession.EvalInteraction("let z = 3")
fsiSession.EvalInteraction("let a = 4")
fsiSession.EvalInteraction("let ccc = 5")
fsiSession.EvalInteraction("let b = 6")
fsiSession.EvalInteraction("let aa = 7")

let values = fsiSession.GetBoundValues() |> List.map (fun x -> x.Value.ReflectionValue)

Assert.AreEqual([4;7;6;5;1;2;3], values)

[<Test>]
let ``Seven bound values are ordered and have their correct type`` () =
use fsiSession = createFsiSession ()

fsiSession.EvalInteraction("let x = 1")
fsiSession.EvalInteraction("let y = 2")
fsiSession.EvalInteraction("let z = 3")
fsiSession.EvalInteraction("let a = 4.")
fsiSession.EvalInteraction("let ccc = 5")
fsiSession.EvalInteraction("let b = 6.f")
fsiSession.EvalInteraction("let aa = 7")

let types = fsiSession.GetBoundValues() |> List.map (fun x -> x.Value.ReflectionType)

Assert.AreEqual([typeof<float>;typeof<int>;typeof<float32>;typeof<int>;typeof<int>;typeof<int>;typeof<int>], types)

[<Test>]
let ``Able to find a bound value by the identifier`` () =
use fsiSession = createFsiSession ()

fsiSession.EvalInteraction("let x = 1")
fsiSession.EvalInteraction("let y = 2")
fsiSession.EvalInteraction("let z = 3")
fsiSession.EvalInteraction("let a = 4")
fsiSession.EvalInteraction("let ccc = 5")
fsiSession.EvalInteraction("let b = 6")
fsiSession.EvalInteraction("let aa = 7")

let boundValueOpt = fsiSession.TryFindBoundValue "ccc"

Assert.IsTrue boundValueOpt.IsSome

[<Test>]
let ``Able to find a bound value by the identifier and has valid info`` () =
use fsiSession = createFsiSession ()

fsiSession.EvalInteraction("let x = 1.")
fsiSession.EvalInteraction("let y = 2.")
fsiSession.EvalInteraction("let z = 3")
fsiSession.EvalInteraction("let a = 4.")
fsiSession.EvalInteraction("let ccc = 5.")
fsiSession.EvalInteraction("let b = 6.")
fsiSession.EvalInteraction("let aa = 7.")

let boundValue = (fsiSession.TryFindBoundValue "z").Value

Assert.AreEqual("z", boundValue.Name)
Assert.AreEqual(3, boundValue.Value.ReflectionValue)
Assert.AreEqual(typeof<int>, boundValue.Value.ReflectionType)

[<Test>]
let ``Not Able to find a bound value by the identifier`` () =
use fsiSession = createFsiSession ()

fsiSession.EvalInteraction("let x = 1")
fsiSession.EvalInteraction("let y = 2")
fsiSession.EvalInteraction("let z = 3")
fsiSession.EvalInteraction("let a = 4")
fsiSession.EvalInteraction("let ccc = 5")
fsiSession.EvalInteraction("let b = 6")
fsiSession.EvalInteraction("let aa = 7")

let boundValueOpt = fsiSession.TryFindBoundValue "aaa"

Assert.IsTrue boundValueOpt.IsNone

[<Test>]
let ``The 'it' value does not exist at the start of a FSI session`` () =
use fsiSession = createFsiSession ()

let boundValueOpt = fsiSession.TryFindBoundValue "it"

Assert.IsTrue boundValueOpt.IsNone

[<Test>]
let ``The 'it' bound value does exists after a value is not explicitly bound`` () =
use fsiSession = createFsiSession ()

fsiSession.EvalInteraction("456")

let boundValueOpt = fsiSession.TryFindBoundValue "it"

Assert.IsTrue boundValueOpt.IsSome

[<Test>]
let ``The 'it' value does exists after a value is not explicitly bound and has valid info`` () =
use fsiSession = createFsiSession ()

fsiSession.EvalInteraction("456")

let boundValue = (fsiSession.TryFindBoundValue "it").Value

Assert.AreEqual("it", boundValue.Name)
Assert.AreEqual(456, boundValue.Value.ReflectionValue)
Assert.AreEqual(typeof<int>, boundValue.Value.ReflectionType)

[<Test>]
let ``The latest shadowed value is only available`` () =
use fsiSession = createFsiSession ()

fsiSession.EvalInteraction("let x = 1")
let boundValue = fsiSession.GetBoundValues() |> List.exactlyOne

Assert.AreEqual("x", boundValue.Name)
Assert.AreEqual(1, boundValue.Value.ReflectionValue)
Assert.AreEqual(typeof<int>, boundValue.Value.ReflectionType)

fsiSession.EvalInteraction("let x = (1, 2)")
let boundValue = fsiSession.GetBoundValues() |> List.exactlyOne

Assert.AreEqual("x", boundValue.Name)
Assert.AreEqual((1, 2), boundValue.Value.ReflectionValue)
Assert.AreEqual(typeof<int * int>, boundValue.Value.ReflectionType)

[<Test>]
let ``The latest shadowed value is only available and can be found`` () =
use fsiSession = createFsiSession ()

fsiSession.EvalInteraction("let x = 1")
let boundValue = (fsiSession.TryFindBoundValue "x").Value

Assert.AreEqual("x", boundValue.Name)
Assert.AreEqual(1, boundValue.Value.ReflectionValue)
Assert.AreEqual(typeof<int>, boundValue.Value.ReflectionType)

fsiSession.EvalInteraction("let x = (1, 2)")
let boundValue = (fsiSession.TryFindBoundValue "x").Value

Assert.AreEqual("x", boundValue.Name)
Assert.AreEqual((1, 2), boundValue.Value.ReflectionValue)
Assert.AreEqual(typeof<int * int>, boundValue.Value.ReflectionType)

[<Test>]
let ``Values are successfully shadowed even with intermediate interactions`` () =
use fsiSession = createFsiSession ()

fsiSession.EvalInteraction("let x = 1")
fsiSession.EvalInteraction("let z = 100")
fsiSession.EvalInteraction("let x = (1, 2)")
fsiSession.EvalInteraction("let w = obj ()")

let boundValues = fsiSession.GetBoundValues()

Assert.AreEqual(3, boundValues.Length)

let boundValue = boundValues |> List.find (fun x -> x.Name = "x")

Assert.AreEqual("x", boundValue.Name)
Assert.AreEqual((1, 2), boundValue.Value.ReflectionValue)
Assert.AreEqual(typeof<int * int>, boundValue.Value.ReflectionType)

let boundValue = (fsiSession.TryFindBoundValue "x").Value

Assert.AreEqual("x", boundValue.Name)
Assert.AreEqual((1, 2), boundValue.Value.ReflectionValue)
Assert.AreEqual(typeof<int * int>, boundValue.Value.ReflectionType)

0 comments on commit 640d3a0

Please sign in to comment.