Skip to content

Commit 727147a

Browse files
authored
The walking dead version of Fixes #9217 (#9363)
* Fix #9217 -- Nuget #r references do not resolve assemblies referenced through the primary one * Regression test * Feedback * triple quote
1 parent aaeb058 commit 727147a

File tree

4 files changed

+104
-54
lines changed

4 files changed

+104
-54
lines changed

src/fsharp/CompileOps.fs

Lines changed: 70 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
module internal FSharp.Compiler.CompileOps
55

66
open System
7+
open System.Collections.Concurrent
78
open System.Collections.Generic
89
open System.Diagnostics
910
open System.IO
@@ -3938,6 +3939,13 @@ and [<Sealed>] TcImports(tcConfigP: TcConfigProvider, initialResolutions: TcAsse
39383939
let mutable dllTable: NameMap<ImportedBinary> = NameMap.empty
39393940
let mutable ccuInfos: ImportedAssembly list = []
39403941
let mutable ccuTable: NameMap<ImportedAssembly> = NameMap.empty
3942+
3943+
/// ccuThunks is a ConcurrentDictionary thus threadsafe
3944+
/// the key is a ccuThunk object, the value is a (unit->unit) func that when executed
3945+
/// the func is used to fix up the func and operates on data captured at the time the func is created.
3946+
/// func() is captured during phase2() of RegisterAndPrepareToImportReferencedDll(..) and PrepareToImportReferencedFSharpAssembly ( .. )
3947+
let mutable ccuThunks = new ConcurrentDictionary<CcuThunk, (unit -> unit)>()
3948+
39413949
let disposeActions = ResizeArray()
39423950
let mutable disposed = false
39433951
let mutable ilGlobalsOpt = ilGlobalsOpt
@@ -3949,14 +3957,33 @@ and [<Sealed>] TcImports(tcConfigP: TcConfigProvider, initialResolutions: TcAsse
39493957
#endif
39503958

39513959
let disposal = new TcImportsSafeDisposal(disposeActions, disposeTypeProviderActions, compilationThread)
3952-
3960+
39533961
let CheckDisposed() =
39543962
if disposed then assert false
39553963

39563964
let dispose () =
39573965
CheckDisposed()
39583966
(disposal :> IDisposable).Dispose()
39593967

3968+
// This is used to fixe up unresolved ccuThunks that were created during assembly import.
3969+
// the ccuThunks dictionary is a ConcurrentDictionary and thus threadsafe.
3970+
// Algorithm:
3971+
// Get a snapshot of the current unFixedUp ccuThunks.
3972+
// for each of those thunks, remove them from the dictionary, so any parallel threads can't do this work
3973+
// If it successfully removed it from the dictionary then do the fixup
3974+
// If the thunk remains unresolved add it back to the ccuThunks dictionary for further processing
3975+
// If not then move on to the next thunk
3976+
let fixupOrphanCcus () =
3977+
let keys = ccuThunks.Keys
3978+
for ccuThunk in keys do
3979+
match ccuThunks.TryRemove(ccuThunk) with
3980+
| true, func ->
3981+
if ccuThunk.IsUnresolvedReference then
3982+
func()
3983+
if ccuThunk.IsUnresolvedReference then
3984+
ccuThunks.TryAdd(ccuThunk, func) |> ignore
3985+
| _ -> ()
3986+
39603987
static let ccuHasType (ccu: CcuThunk) (nsname: string list) (tname: string) =
39613988
let matchNameSpace (entityOpt: Entity option) n =
39623989
match entityOpt with
@@ -3988,13 +4015,13 @@ and [<Sealed>] TcImports(tcConfigP: TcConfigProvider, initialResolutions: TcAsse
39884015
CheckDisposed()
39894016
tcImportsWeak
39904017
#endif
3991-
4018+
39924019
member tcImports.RegisterCcu ccuInfo =
39934020
CheckDisposed()
39944021
ccuInfos <- ccuInfos ++ ccuInfo
39954022
// Assembly Ref Resolution: remove this use of ccu.AssemblyName
39964023
ccuTable <- NameMap.add (ccuInfo.FSharpViewOfMetadata.AssemblyName) ccuInfo ccuTable
3997-
4024+
39984025
member tcImports.RegisterDll dllInfo =
39994026
CheckDisposed()
40004027
dllInfos <- dllInfos ++ dllInfo
@@ -4037,24 +4064,24 @@ and [<Sealed>] TcImports(tcConfigP: TcConfigProvider, initialResolutions: TcAsse
40374064
| Some res -> res
40384065
| None -> error(Error(FSComp.SR.buildCouldNotResolveAssembly assemblyName, m))
40394066

4040-
member tcImports.GetImportedAssemblies() =
4067+
member tcImports.GetImportedAssemblies() =
40414068
CheckDisposed()
4042-
match importsBase with
4069+
match importsBase with
40434070
| Some importsBase-> List.append (importsBase.GetImportedAssemblies()) ccuInfos
4044-
| None -> ccuInfos
4045-
4046-
member tcImports.GetCcusExcludingBase() =
4071+
| None -> ccuInfos
4072+
4073+
member tcImports.GetCcusExcludingBase() =
40474074
CheckDisposed()
4048-
ccuInfos |> List.map (fun x -> x.FSharpViewOfMetadata)
4075+
ccuInfos |> List.map (fun x -> x.FSharpViewOfMetadata)
40494076

4050-
member tcImports.GetCcusInDeclOrder() =
4077+
member tcImports.GetCcusInDeclOrder() =
40514078
CheckDisposed()
40524079
List.map (fun x -> x.FSharpViewOfMetadata) (tcImports.GetImportedAssemblies())
4053-
4080+
40544081
// This is the main "assembly reference --> assembly" resolution routine.
4055-
member tcImports.FindCcuInfo (ctok, m, assemblyName, lookupOnly) =
4082+
member tcImports.FindCcuInfo (ctok, m, assemblyName, lookupOnly) =
40564083
CheckDisposed()
4057-
let rec look (t: TcImports) =
4084+
let rec look (t: TcImports) =
40584085
match NameMap.tryFind assemblyName t.CcuTable with
40594086
| Some res -> Some res
40604087
| None ->
@@ -4069,9 +4096,8 @@ and [<Sealed>] TcImports(tcConfigP: TcConfigProvider, initialResolutions: TcAsse
40694096
match look tcImports with
40704097
| Some res -> ResolvedImportedAssembly res
40714098
| None -> UnresolvedImportedAssembly assemblyName
4072-
40734099

4074-
member tcImports.FindCcu (ctok, m, assemblyName, lookupOnly) =
4100+
member tcImports.FindCcu (ctok, m, assemblyName, lookupOnly) =
40754101
CheckDisposed()
40764102
match tcImports.FindCcuInfo(ctok, m, assemblyName, lookupOnly) with
40774103
| ResolvedImportedAssembly importedAssembly -> ResolvedCcu(importedAssembly.FSharpViewOfMetadata)
@@ -4509,7 +4535,7 @@ and [<Sealed>] TcImports(tcConfigP: TcConfigProvider, initialResolutions: TcAsse
45094535
#endif
45104536
FSharpOptimizationData = notlazy None }
45114537
tcImports.RegisterCcu ccuinfo
4512-
let phase2 () =
4538+
let phase2 () =
45134539
#if !NO_EXTENSIONTYPING
45144540
ccuinfo.TypeProviders <- tcImports.ImportTypeProviderExtensions (ctok, tcConfig, filename, ilScopeRef, ilModule.ManifestOfAssembly.CustomAttrs.AsList, ccu.Contents, invalidateCcu, m)
45154541
#endif
@@ -4569,11 +4595,17 @@ and [<Sealed>] TcImports(tcConfigP: TcConfigProvider, initialResolutions: TcAsse
45694595
| None ->
45704596
if verbose then dprintf "*** no optimization data for CCU %s, was DLL compiled with --no-optimization-data??\n" ccuName
45714597
None
4572-
| Some info ->
4598+
| Some info ->
45734599
let data = GetOptimizationData (filename, ilScopeRef, ilModule.TryGetILModuleDef(), info)
4574-
let res = data.OptionalFixup(fun nm -> availableToOptionalCcu(tcImports.FindCcu(ctok, m, nm, lookupOnly=false)))
4575-
if verbose then dprintf "found optimization data for CCU %s\n" ccuName
4576-
Some res)
4600+
let fixupThunk () = data.OptionalFixup(fun nm -> availableToOptionalCcu(tcImports.FindCcu(ctok, m, nm, lookupOnly=false)))
4601+
4602+
// Make a note of all ccuThunks that may still need to be fixed up when other dlls are loaded
4603+
for ccuThunk in data.FixupThunks do
4604+
if ccuThunk.IsUnresolvedReference then
4605+
ccuThunks.TryAdd(ccuThunk, fun () -> fixupThunk () |> ignore) |> ignore
4606+
4607+
if verbose then dprintf "found optimization data for CCU %s\n" ccuName
4608+
Some (fixupThunk ()))
45774609

45784610
let ilg = defaultArg ilGlobalsOpt EcmaMscorlibILGlobals
45794611

@@ -4599,19 +4631,25 @@ and [<Sealed>] TcImports(tcConfigP: TcConfigProvider, initialResolutions: TcAsse
45994631
()
46004632
#endif
46014633
data, ccuinfo, phase2)
4602-
4634+
46034635
// Register all before relinking to cope with mutually-referential ccus
46044636
ccuRawDataAndInfos |> List.iter (p23 >> tcImports.RegisterCcu)
4605-
let phase2 () =
4637+
let phase2 () =
46064638
(* Relink *)
46074639
(* dprintf "Phase2: %s\n" filename; REMOVE DIAGNOSTICS *)
4608-
ccuRawDataAndInfos |> List.iter (fun (data, _, _) -> data.OptionalFixup(fun nm -> availableToOptionalCcu(tcImports.FindCcu(ctok, m, nm, lookupOnly=false))) |> ignore)
4640+
ccuRawDataAndInfos
4641+
|> List.iter (fun (data, _, _) ->
4642+
let fixupThunk () = data.OptionalFixup(fun nm -> availableToOptionalCcu(tcImports.FindCcu(ctok, m, nm, lookupOnly=false))) |> ignore
4643+
fixupThunk()
4644+
for ccuThunk in data.FixupThunks do
4645+
if ccuThunk.IsUnresolvedReference then
4646+
ccuThunks.TryAdd(ccuThunk, fixupThunk) |> ignore
4647+
)
46094648
#if !NO_EXTENSIONTYPING
46104649
ccuRawDataAndInfos |> List.iter (fun (_, _, phase2) -> phase2())
46114650
#endif
4612-
ccuRawDataAndInfos |> List.map p23 |> List.map ResolvedImportedAssembly
4651+
ccuRawDataAndInfos |> List.map p23 |> List.map ResolvedImportedAssembly
46134652
phase2
4614-
46154653

46164654
// NOTE: When used in the Language Service this can cause the transitive checking of projects. Hence it must be cancellable.
46174655
member tcImports.RegisterAndPrepareToImportReferencedDll (ctok, r: AssemblyResolution) : Cancellable<_ * (unit -> AvailableImportedAssembly list)> =
@@ -4653,16 +4691,16 @@ and [<Sealed>] TcImports(tcConfigP: TcConfigProvider, initialResolutions: TcAsse
46534691
ILAssemblyRefs = assemblyData.ILAssemblyRefs }
46544692
tcImports.RegisterDll dllinfo
46554693
let ilg = defaultArg ilGlobalsOpt EcmaMscorlibILGlobals
4656-
let phase2 =
4694+
let phase2 =
46574695
if assemblyData.HasAnyFSharpSignatureDataAttribute then
46584696
if not (assemblyData.HasMatchingFSharpSignatureDataAttribute ilg) then
4659-
errorR(Error(FSComp.SR.buildDifferentVersionMustRecompile filename, m))
4660-
tcImports.PrepareToImportReferencedILAssembly (ctok, m, filename, dllinfo)
4697+
errorR(Error(FSComp.SR.buildDifferentVersionMustRecompile filename, m))
4698+
tcImports.PrepareToImportReferencedILAssembly (ctok, m, filename, dllinfo)
46614699
else
4662-
try
4700+
try
46634701
tcImports.PrepareToImportReferencedFSharpAssembly (ctok, m, filename, dllinfo)
4664-
with e -> error(Error(FSComp.SR.buildErrorOpeningBinaryFile(filename, e.Message), m))
4665-
else
4702+
with e -> error(Error(FSComp.SR.buildErrorOpeningBinaryFile(filename, e.Message), m))
4703+
else
46664704
tcImports.PrepareToImportReferencedILAssembly (ctok, m, filename, dllinfo)
46674705
return dllinfo, phase2
46684706
}
@@ -4683,6 +4721,7 @@ and [<Sealed>] TcImports(tcConfigP: TcConfigProvider, initialResolutions: TcAsse
46834721
})
46844722

46854723
let dllinfos, phase2s = results |> List.choose id |> List.unzip
4724+
fixupOrphanCcus()
46864725
let ccuinfos = (List.collect (fun phase2 -> phase2()) phase2s)
46874726
return dllinfos, ccuinfos
46884727
}

src/fsharp/TypedTree.fs

Lines changed: 5 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5021,28 +5021,21 @@ type CcuReference = string // ILAssemblyRef
50215021
/// reference that has not had an appropriate fixup applied.
50225022
[<NoEquality; NoComparison; RequireQualifiedAccess; StructuredFormatDisplay("{DebugText}")>]
50235023
type CcuThunk =
5024-
50255024
{
5025+
/// ccu.target is null when a reference is missing in the transitive closure of static references that
5026+
/// may potentially be required for the metadata of referenced DLLs.
50265027
mutable target: CcuData
5027-
5028-
/// ccu.orphanfixup is true when a reference is missing in the transitive closure of static references that
5029-
/// may potentially be required for the metadata of referenced DLLs. It is set to true if the "loader"
5030-
/// used in the F# metadata-deserializer or the .NET metadata reader returns a failing value (e.g. None).
5031-
/// Note: When used from Visual Studio, the loader will not automatically chase down transitively referenced DLLs - they
5032-
/// must be in the explicit references in the project.
5033-
mutable orphanfixup: bool
5034-
50355028
name: CcuReference
50365029
}
50375030

50385031
/// Dereference the asssembly reference
50395032
member ccu.Deref =
5040-
if isNull (ccu.target :> obj) || ccu.orphanfixup then
5033+
if isNull (ccu.target :> obj) then
50415034
raise(UnresolvedReferenceNoRange ccu.name)
50425035
ccu.target
50435036

50445037
/// Indicates if this assembly reference is unresolved
5045-
member ccu.IsUnresolvedReference = isNull (ccu.target :> obj) || ccu.orphanfixup
5038+
member ccu.IsUnresolvedReference = isNull (ccu.target :> obj)
50465039

50475040
/// Ensure the ccu is derefable in advance. Supply a path to attach to any resulting error message.
50485041
member ccu.EnsureDerefable(requiringPath: string[]) =
@@ -5104,13 +5097,11 @@ type CcuThunk =
51045097
/// Create a CCU with the given name and contents
51055098
static member Create(nm, x) =
51065099
{ target = x
5107-
orphanfixup = false
51085100
name = nm }
51095101

51105102
/// Create a CCU with the given name but where the contents have not yet been specified
51115103
static member CreateDelayed nm =
51125104
{ target = Unchecked.defaultof<_>
5113-
orphanfixup = false
51145105
name = nm }
51155106

51165107
/// Fixup a CCU to have the given contents
@@ -5128,13 +5119,7 @@ type CcuThunk =
51285119
match box avail.target with
51295120
| null -> error(Failure("internal error: ccu thunk '"+avail.name+"' not fixed up!"))
51305121
| _ -> avail.target
5131-
5132-
/// Fixup a CCU to record it as "orphaned", i.e. not available
5133-
member x.FixupOrphaned() =
5134-
match box x.target with
5135-
| null -> x.orphanfixup<-true
5136-
| _ -> errorR(Failure("internal error: FixupOrphaned: the ccu thunk for assembly "+x.AssemblyName+" not delayed!"))
5137-
5122+
51385123
/// Try to resolve a path into the CCU by referencing the .NET/CLI type forwarder table of the CCU
51395124
member ccu.TryForward(nlpath: string[], item: string) : EntityRef option =
51405125
ccu.EnsureDerefable nlpath

src/fsharp/TypedTreePickle.fs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,11 @@ type PickledDataWithReferences<'rawData> =
5050
member x.OptionalFixup loader =
5151
x.FixupThunks
5252
|> Array.iter(fun reqd->
53-
match loader reqd.AssemblyName with
54-
| Some loaded -> reqd.Fixup loaded
55-
| None -> reqd.FixupOrphaned() )
53+
// Only fixup what needs fixing up
54+
if reqd.IsUnresolvedReference then
55+
match loader reqd.AssemblyName with
56+
| Some loaded -> reqd.Fixup loaded
57+
| _ -> () )
5658
x.RawData
5759

5860
//---------------------------------------------------------------------------

tests/FSharp.Compiler.Private.Scripting.UnitTests/FSharpScriptTests.fs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,3 +302,27 @@ let x =
302302
"
303303
script.Eval(code) |> ignoreValue
304304
Assert.False(foundInner)
305+
306+
[<Test>]
307+
member _.``Script with nuget package that yields out of order dependencies works correctly``() =
308+
// regression test for: https://github.com/dotnet/fsharp/issues/9217
309+
310+
let code = """
311+
#r "nuget: FParsec,1.1.1"
312+
313+
open FParsec
314+
315+
let test p str =
316+
match run p str with
317+
| Success(result, _, _) ->
318+
printfn "Success: %A" result
319+
true
320+
| Failure(errorMsg, _, _) ->
321+
printfn "Failure: %s" errorMsg
322+
false
323+
test pfloat "1.234"
324+
"""
325+
use script = new FSharpScript(additionalArgs=[|"/langversion:preview"|])
326+
let opt = script.Eval(code) |> getValue
327+
let value = opt.Value
328+
Assert.AreEqual(true, value.ReflectionValue :?> bool)

0 commit comments

Comments
 (0)