Skip to content

Commit 348ed06

Browse files
authored
General AddBoundValue fixes, test coverage, and slight API change (#9234)
* Changed AddBoundValue API to throw exceptions instead of returning error list. * Fixed generic types with AddBoundValue * Added failing test * Added more tests * Added function tests * Special case functions even more * Minor name change
1 parent 53ed728 commit 348ed06

4 files changed

Lines changed: 313 additions & 162 deletions

File tree

src/fsharp/fsi/fsi.fs

Lines changed: 132 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -959,49 +959,64 @@ let internal WithImplicitHome (tcConfigB, dir) f =
959959
try f()
960960
finally tcConfigB.implicitIncludeDir <- old
961961

962-
let internal importReflectionType amap (reflectionTy: Type) =
963-
let reflectionTyToILTypeRef (reflectionTy: Type) =
964-
let aref = ILAssemblyRef.FromAssemblyName(reflectionTy.Assembly.GetName())
965-
let scoref = ILScopeRef.Assembly aref
966-
967-
let fullName = reflectionTy.FullName
968-
let index = fullName.IndexOf("[")
969-
let fullName =
970-
if index = -1 then
971-
fullName
972-
else
973-
fullName.Substring(0, index - 1)
962+
let internal convertReflectionTypeToILTypeRef (reflectionTy: Type) =
963+
if reflectionTy.Assembly.IsDynamic then
964+
raise (NotSupportedException(sprintf "Unable to import type, %A, from a dynamic assembly." reflectionTy))
965+
966+
if not reflectionTy.IsPublic && not reflectionTy.IsNestedPublic then
967+
invalidOp (sprintf "Cannot import the non-public type, %A." reflectionTy)
974968

975-
let isTop = reflectionTy.DeclaringType = null
976-
if isTop then
977-
ILTypeRef.Create(scoref, [], fullName)
969+
let aref = ILAssemblyRef.FromAssemblyName(reflectionTy.Assembly.GetName())
970+
let scoref = ILScopeRef.Assembly aref
971+
972+
let fullName = reflectionTy.FullName
973+
let index = fullName.IndexOf("[")
974+
let fullName =
975+
if index = -1 then
976+
fullName
978977
else
979-
let names = String.split StringSplitOptions.None [|"+";"."|] fullName
980-
let enc = names.[..names.Length - 2]
981-
let nm = names.[names.Length - 1]
982-
ILTypeRef.Create(scoref, List.ofArray enc, nm)
983-
984-
let rec reflectionTyToILType (reflectionTy: Type) =
985-
let tref = reflectionTyToILTypeRef reflectionTy
986-
let genericArgs =
987-
reflectionTy.GenericTypeArguments
988-
|> Seq.map reflectionTyToILType
989-
|> List.ofSeq
990-
991-
let boxity =
992-
if reflectionTy.IsValueType then
993-
ILBoxity.AsValue
978+
fullName.Substring(0, index)
979+
980+
let isTop = reflectionTy.DeclaringType = null
981+
if isTop then
982+
ILTypeRef.Create(scoref, [], fullName)
983+
else
984+
let names = String.split StringSplitOptions.None [|"+";"."|] fullName
985+
let enc = names.[..names.Length - 2]
986+
let nm = names.[names.Length - 1]
987+
ILTypeRef.Create(scoref, List.ofArray enc, nm)
988+
989+
let rec internal convertReflectionTypeToILType (reflectionTy: Type) =
990+
let reflectionTy =
991+
// Special case functions.
992+
if FSharp.Reflection.FSharpType.IsFunction reflectionTy then
993+
let ctors = reflectionTy.GetConstructors(BindingFlags.Public ||| BindingFlags.NonPublic ||| BindingFlags.Instance)
994+
if ctors.Length = 1 &&
995+
ctors.[0].GetCustomAttribute<CompilerGeneratedAttribute>() <> null &&
996+
not ctors.[0].IsPublic &&
997+
PrettyNaming.IsCompilerGeneratedName reflectionTy.Name then
998+
let rec get (typ: Type) = if FSharp.Reflection.FSharpType.IsFunction typ.BaseType then get typ.BaseType else typ
999+
get reflectionTy
9941000
else
995-
ILBoxity.AsObject
1001+
reflectionTy
1002+
else
1003+
reflectionTy
9961004

997-
let tspec = ILTypeSpec.Create(tref, genericArgs)
1005+
let tref = convertReflectionTypeToILTypeRef reflectionTy
1006+
let genericArgs =
1007+
reflectionTy.GenericTypeArguments
1008+
|> Seq.map convertReflectionTypeToILType
1009+
|> List.ofSeq
9981010

999-
mkILTy boxity tspec
1011+
let boxity =
1012+
if reflectionTy.IsValueType then
1013+
ILBoxity.AsValue
1014+
else
1015+
ILBoxity.AsObject
10001016

1001-
let rec import (ilTy: ILType) =
1002-
Import.ImportILType amap range0 (ilTy.GenericArgs |> List.map import) ilTy
1017+
let tspec = ILTypeSpec.Create(tref, genericArgs)
10031018

1004-
import (reflectionTyToILType reflectionTy)
1019+
mkILTy boxity tspec
10051020

10061021
let internal mkBoundValueTypedImpl tcGlobals m moduleName name ty =
10071022
let vis = Accessibility.TAccess([])
@@ -1507,51 +1522,90 @@ type internal FsiDynamicCompiler
15071522
| _ ->
15081523
None
15091524

1510-
member __.AddBoundValue (ctok, errorLogger: ErrorLogger, istate, name: string, value: obj) =
1511-
if String.IsNullOrWhiteSpace name then
1512-
errorLogger.Error(Error(FSComp.SR.parsIdentifierExpected(), range0))
1525+
member private this.ImportReflectionType (ctok, istate, reflectionTy) =
1526+
let amap = istate.tcImports.GetImportMap()
1527+
1528+
let resolveAssemblyRefOfILType istate (ilTy: ILType) =
1529+
let tcImports = istate.tcImports
1530+
1531+
if ilTy.IsNominal then
1532+
match ilTy.TypeRef.Scope with
1533+
| ILScopeRef.Assembly aref ->
1534+
// Simple name unification. If it fails, then try to resolve the assembly.
1535+
if tcImports.TryFindDllInfo(ctok, range0, aref.Name, lookupOnly = true).IsNone then
1536+
this.EvalRequireReference(ctok, istate, range0, aref.Name)
1537+
|> snd
1538+
else
1539+
istate
1540+
| _ ->
1541+
istate
1542+
else
1543+
istate
1544+
1545+
let rec import istate (ilTy: ILType) =
1546+
let istate = resolveAssemblyRefOfILType istate ilTy
1547+
let istate, tinst =
1548+
(ilTy.GenericArgs, (istate, []))
1549+
||> List.foldBack (fun ilGenericArgTy (istate, tinst) ->
1550+
let istate, ty = import istate ilGenericArgTy
1551+
(istate, ty :: tinst))
1552+
istate, Import.ImportILType amap range0 tinst ilTy
1553+
1554+
import istate (convertReflectionTypeToILType reflectionTy)
15131555

1514-
// Verify that the name is a valid identifier for a value.
1515-
SourceCodeServices.Lexer.FSharpLexer.Lex(SourceText.ofString name,
1516-
let mutable foundOne = false
1517-
fun t ->
1518-
if not t.IsIdentifier || foundOne then
1519-
errorLogger.Error(Error(FSComp.SR.parsIdentifierExpected(), range0))
1520-
foundOne <- true)
1556+
member this.AddBoundValue (ctok, errorLogger: ErrorLogger, istate, name: string, value: obj) =
1557+
try
1558+
match value with
1559+
| null -> nullArg "value"
1560+
| _ -> ()
15211561

1522-
if PrettyNaming.IsCompilerGeneratedName name then
1523-
errorLogger.Warning(Error(FSComp.SR.lexhlpIdentifiersContainingAtSymbolReserved(), range0))
1562+
if String.IsNullOrWhiteSpace name then
1563+
invalidArg "name" "Name cannot be null or white-space."
15241564

1525-
let amap = istate.tcImports.GetImportMap()
1526-
let ty = importReflectionType amap (value.GetType())
1565+
// Verify that the name is a valid identifier for a value.
1566+
SourceCodeServices.Lexer.FSharpLexer.Lex(SourceText.ofString name,
1567+
let mutable foundOne = false
1568+
fun t ->
1569+
if not t.IsIdentifier || foundOne then
1570+
invalidArg "name" "Name is not a valid identifier."
1571+
foundOne <- true)
15271572

1528-
let i = nextFragmentId()
1529-
let prefix = mkFragmentPath i
1530-
let prefixPath = pathOfLid prefix
1531-
let qualifiedName = ComputeQualifiedNameOfFileFromUniquePath (rangeStdin,prefixPath)
1573+
if PrettyNaming.IsCompilerGeneratedName name then
1574+
invalidArg "name" (FSComp.SR.lexhlpIdentifiersContainingAtSymbolReserved() |> snd)
15321575

1533-
let tcConfig = TcConfig.Create(tcConfigB,validate=false)
1576+
let istate, ty = this.ImportReflectionType(ctok, istate, value.GetType())
1577+
let amap = istate.tcImports.GetImportMap()
15341578

1535-
// Build a simple module with a single 'let' decl with a default value.
1536-
let moduleOrNamespace, v, impl = mkBoundValueTypedImpl istate.tcGlobals range0 qualifiedName.Text name ty
1537-
let tcEnvAtEndOfLastInput =
1538-
TypeChecker.AddLocalSubModule tcGlobals amap range0 istate.tcState.TcEnvFromImpls moduleOrNamespace
1539-
|> TypeChecker.AddLocalVal TcResultsSink.NoSink range0 v
1579+
let i = nextFragmentId()
1580+
let prefix = mkFragmentPath i
1581+
let prefixPath = pathOfLid prefix
1582+
let qualifiedName = ComputeQualifiedNameOfFileFromUniquePath (rangeStdin,prefixPath)
15401583

1541-
// Generate IL for the given typled impl and create new interactive state.
1542-
let ilxGenerator = istate.ilxGenerator
1543-
let isIncrementalFragment = true
1544-
let showTypes = false
1545-
let declaredImpls = [impl]
1546-
let codegenResults, optEnv, fragName = ProcessTypedImpl(errorLogger, istate.optEnv, istate.tcState, tcConfig, false, EmptyTopAttrs, prefix, isIncrementalFragment, declaredImpls, ilxGenerator)
1547-
let istate, declaredImpls = ProcessCodegenResults(ctok, errorLogger, istate, optEnv, istate.tcState, tcConfig, prefix, showTypes, isIncrementalFragment, fragName, declaredImpls, ilxGenerator, codegenResults)
1548-
let newState = { istate with tcState = istate.tcState.NextStateAfterIncrementalFragment tcEnvAtEndOfLastInput }
1549-
1550-
// Force set the val with the given value obj.
1551-
let ctxt = valuePrinter.GetEvaluationContext(newState.emEnv)
1552-
ilxGenerator.ForceSetGeneratedValue(ctxt, v, value)
1553-
1554-
fst (processContents newState declaredImpls)
1584+
let tcConfig = TcConfig.Create(tcConfigB,validate=false)
1585+
1586+
// Build a simple module with a single 'let' decl with a default value.
1587+
let moduleOrNamespace, v, impl = mkBoundValueTypedImpl istate.tcGlobals range0 qualifiedName.Text name ty
1588+
let tcEnvAtEndOfLastInput =
1589+
TypeChecker.AddLocalSubModule tcGlobals amap range0 istate.tcState.TcEnvFromImpls moduleOrNamespace
1590+
|> TypeChecker.AddLocalVal TcResultsSink.NoSink range0 v
1591+
1592+
// Generate IL for the given typled impl and create new interactive state.
1593+
let ilxGenerator = istate.ilxGenerator
1594+
let isIncrementalFragment = true
1595+
let showTypes = false
1596+
let declaredImpls = [impl]
1597+
let codegenResults, optEnv, fragName = ProcessTypedImpl(errorLogger, istate.optEnv, istate.tcState, tcConfig, false, EmptyTopAttrs, prefix, isIncrementalFragment, declaredImpls, ilxGenerator)
1598+
let istate, declaredImpls = ProcessCodegenResults(ctok, errorLogger, istate, optEnv, istate.tcState, tcConfig, prefix, showTypes, isIncrementalFragment, fragName, declaredImpls, ilxGenerator, codegenResults)
1599+
let newState = { istate with tcState = istate.tcState.NextStateAfterIncrementalFragment tcEnvAtEndOfLastInput }
1600+
1601+
// Force set the val with the given value obj.
1602+
let ctxt = valuePrinter.GetEvaluationContext(newState.emEnv)
1603+
ilxGenerator.ForceSetGeneratedValue(ctxt, v, value)
1604+
1605+
processContents newState declaredImpls
1606+
with
1607+
| ex ->
1608+
istate, CompletedWithReportedError(StopProcessingExn(Some ex))
15551609

15561610
member __.GetInitialInteractiveState () =
15571611
let tcConfig = TcConfig.Create(tcConfigB,validate=false)
@@ -2412,8 +2466,8 @@ type internal FsiInteractionProcessor
24122466
member __.AddBoundValue(ctok, errorLogger, name, value: obj) =
24132467
currState
24142468
|> InteractiveCatch errorLogger (fun istate ->
2415-
fsiDynamicCompiler.AddBoundValue(ctok, errorLogger, istate, name, value), Completed None) |> fst
2416-
|> setCurrState
2469+
fsiDynamicCompiler.AddBoundValue(ctok, errorLogger, istate, name, value))
2470+
|> commitResult
24172471

24182472
member __.PartialAssemblySignatureUpdated = event.Publish
24192473

@@ -2937,20 +2991,14 @@ type FsiEvaluationSession (fsi: FsiEvaluationSessionHostConfig, argv:string[], i
29372991
fsiDynamicCompiler.TryFindBoundValue(fsiInteractionProcessor.CurrentState, name)
29382992

29392993
member __.AddBoundValue(name: string, value: obj) =
2940-
match value with
2941-
| null -> nullArg "value"
2942-
| _ -> ()
2943-
29442994
// Explanation: When the user of the FsiInteractiveSession object calls this method, the
29452995
// code is parsed, checked and evaluated on the calling thread. This means EvalExpression
29462996
// is not safe to call concurrently.
29472997
let ctok = AssumeCompilationThreadWithoutEvidence()
29482998

2949-
let errorOptions = TcConfig.Create(tcConfigB, validate = false).errorSeverityOptions
2950-
let errorLogger = CompilationErrorLogger("AddBoundValue", errorOptions)
29512999
fsiInteractionProcessor.AddBoundValue(ctok, errorLogger, name, value)
2952-
let errs = errorLogger.GetErrors()
2953-
ErrorHelpers.CreateErrorInfos (errorOptions, true, "input.fsx", errs, true)
3000+
|> commitResult
3001+
|> ignore
29543002

29553003
/// Performs these steps:
29563004
/// - Load the dummy interaction, if any

src/fsharp/fsi/fsi.fsi

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,7 @@ type FsiEvaluationSession =
269269
/// Creates a root-level value with the given name and .NET object.
270270
/// If the .NET object contains types from assemblies that are not referenced in the interactive session, it will try to implicitly resolve them by default configuration.
271271
/// Name must be a valid identifier.
272-
member AddBoundValue : name: string * value: obj -> FSharpErrorInfo[]
272+
member AddBoundValue : name: string * value: obj -> unit
273273

274274
/// Load the dummy interaction, load the initial files, and,
275275
/// if interacting, start the background thread to read the standard input.

src/fsharp/service/ServiceLexing.fs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1390,10 +1390,9 @@ module Lexer =
13901390
use _unwindBP = PushThreadBuildPhaseUntilUnwind BuildPhase.Parse
13911391
use _unwindEL = PushErrorLoggerPhaseUntilUnwind (fun _ -> DiscardErrorsLogger)
13921392

1393-
usingLexbufForParsing (lexbuf, filePath) (fun lexbuf ->
1394-
while not lexbuf.IsPastEndOfStream do
1395-
ct.ThrowIfCancellationRequested ()
1396-
onToken (getNextToken lexbuf) lexbuf.LexemeRange)
1393+
while not lexbuf.IsPastEndOfStream do
1394+
ct.ThrowIfCancellationRequested ()
1395+
onToken (getNextToken lexbuf) lexbuf.LexemeRange
13971396

13981397
let lex text filePath conditionalCompilationDefines flags supportsFeature lexCallback pathMap ct =
13991398
let errorLogger = CompilationErrorLogger("Lexer", ErrorLogger.FSharpErrorSeverityOptions.Default)

0 commit comments

Comments
 (0)