Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge master to feature/witness-passing #9077

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)