Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 95ff08f

Browse files
T-GroCopilotCopilot
authored
Fix spurious type errors for provided types under parallel compilation (#19969)
* Fix spurious type errors for provided types under parallel compilation Intern provided-type entities by name so the same provided type linked from multiple files under graph-based parallel checking yields one entity, avoiding spurious FS0001 type mismatches from identity comparisons. Capture the systemRuntimeContainsType closure stably so TypeProviders-SDK providers load under an unoptimized compiler (the SDK reflects on a field named tcImports whose name was previously codegen-dependent). Co-authored-by: Copilot <[email protected]> * Add release notes for type-provider parallel-checking and SDK hosting fixes Co-authored-by: Copilot <[email protected]> * Retrigger CI: IcedTasks_Test_Debug flaky timeout (agent OOM, net9.0 testhost hang) All 47 jobs passed except IcedTasks_Test_Debug, which timed out at 120min after all but one test target completed. The identical suite passed in IcedTasks_Test_Release (8m) and IcedTasks does not use type providers, so this PR's type-provider-only changes cannot affect its build or test runtime. The CI agent reported 95% memory usage and one net9.0 testhost hung. Re-running to clear the transient failure. Co-authored-by: Copilot <[email protected]> * Address review feedback: harden provided-type cache coherence, strengthen guard test, document embedding path - TypedTree.fs: serialize the four lookup caches AddProvidedTypeEntity invalidates against concurrent provided-type interning via a lazily-created lock gated on a volatile hostsProvidedTypes flag (cacheOptByrefWithLock), so non-type-provider modules keep the lock-free fast path. Replaces the CAS append with a lock-guarded append+invalidate, closing the read-compute-store vs invalidation race. - lib.fs/lib.fsi: add cacheOptByrefWithLock inline helper. - ManglingNameOfProvidedTypes.fs: strengthen the systemRuntimeContainsType guard test (also assert no synthesized 'objectArg' receiver capture) and document/verify it in Debug and Release; add a concurrency stress test for cache coherence under interning. - CheckDeclarations.fs: comment confirming the embedding path builds a fresh local module disjoint from interned provided-type modules. Co-authored-by: Copilot <[email protected]> * Make provided-type lookup caches lock-free via entity version stamping Replace the lock-based coherence scheme for ModuleOrNamespaceType's provided-type-mutated lookup tables with a lock-free version-stamped approach. 'entitiesVersion' is bumped (release) whenever 'entities' is appended to, and each cached lookup table is tagged with the version it was built from, so a reader on another graph-based-checking thread recomputes after a concurrent provided-type append instead of trusting a stale memo. - lib: replace 'cacheOptByrefWithLock' with 'cacheOptByrefByVersion'. - TypedTree: drop 'providedTypeCacheLock'/'hostsProvidedTypes' and the 'ProvidedTypeCacheGate'; 'AddProvidedTypeEntity' now appends via a CAS loop and bumps 'entitiesVersion' last; the version-stamped caches need no explicit invalidation. - Tests: exercise all four version-stamped lookup tables with more concurrent readers. Co-authored-by: Copilot <[email protected]> * Bound the provided-type cache-coherence stress test for CI The concurrency guard spun 32 hot reader threads against 100 writers over 20 iterations, continuously rebuilding the version-stamped lookup tables under Server GC. That pegs CPU and piles up allocations on memory-limited CI agents. Reduce to 6 cooperative (yielding) readers and 40 writers over 5 iterations: still interleaves many version bumps with concurrent reads to guard the coherence invariant, without the resource spike. Co-authored-by: Copilot <[email protected]> --------- Co-authored-by: Copilot <[email protected]> Co-authored-by: Copilot <[email protected]>
1 parent 4d06bd1 commit 95ff08f

11 files changed

Lines changed: 291 additions & 37 deletions

File tree

docs/release-notes/.FSharp.Compiler.Service/11.0.100.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
### Fixed
22

3+
* Provided types used from multiple files no longer produce spurious FS0001 type mismatches under parallel compilation; provided-type entities are now interned so every file linking a given provided type shares one entity. ([PR #19969](https://github.com/dotnet/fsharp/pull/19969))
4+
* TypeProviders-SDK providers now load under an unoptimized compiler; the `systemRuntimeContainsType` closure field the SDK reflects on (`tcImports`) is captured stably regardless of optimization settings. ([PR #19969](https://github.com/dotnet/fsharp/pull/19969))
35
* Fixed: Inheriting from an undefined type now reports `FS0039` exactly once instead of three times. Phase 1F and Phase 2A of inherit-clause type-checking now skip re-resolving a syntactic clause whose Phase 1D resolution already failed with `UndefinedName`, eliminating both the duplicate diagnostic and the redundant work. ([Issue #16432](https://github.com/dotnet/fsharp/issues/16432), [PR #19862](https://github.com/dotnet/fsharp/pull/19862))
46
* Fix several F# editor semantic-classification errors: F# delegate declarations no longer highlight the `delegate of …` syntax as a method, computation-expression builders inside list/array comprehensions are classified as `ComputationExpression`, the closing `]` of an open-ended slice (e.g. `xs[0..]`) is no longer classified as `Function`/`Method`, and `open type T` is no longer reported as unused when its imported members (static members, static fields, or DU union cases) are used. ([Issue #19905](https://github.com/dotnet/fsharp/issues/19905), [PR #19960](https://github.com/dotnet/fsharp/pull/19960))
57
* Diagnostic FS0027 now emits a parameter-specific message (suggesting a `let mutable x = x` shadow or `byref<_>`) instead of the illegal `let mutable x = expression` shadow when the assignment target is a function or method parameter. ([Issue #15803](https://github.com/dotnet/fsharp/issues/15803), [PR #19866](https://github.com/dotnet/fsharp/pull/19866))

src/Compiler/Checking/CheckDeclarations.fs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3147,6 +3147,12 @@ module EstablishTypeDefinitionCores =
31473147
let cpath = eref.CompilationPath.NestedCompPath eref.LogicalName ModuleOrNamespaceKind.ModuleOrType
31483148
let access = combineAccess tycon.Accessibility (if st.PUntaint((fun st -> st.IsPublic || st.IsNestedPublic), m) then taccessPublic else taccessPrivate cpath)
31493149

3150+
// Embed the type into the module we're compiling. This is a freshly-built local module for the
3151+
// 'type X = ABC<...>' generated-type set (see mkLocalTyconRef above), disjoint from the referenced
3152+
// provided-type modules that GetOrInternProvidedEntity interns into, and each generated set is
3153+
// built once on a single file's checking thread. There is therefore no cross-file same-mangled-name
3154+
// collision here, so adding the entity directly (rather than via GetOrInternProvidedEntity) cannot
3155+
// diverge the intern table from the 'entities' queue.
31503156
let nestedTycon = Construct.NewProvidedTycon(resolutionEnvironment, st,
31513157
Import.ImportProvidedType cenv.amap m,
31523158
isSuppressRelocate,

src/Compiler/Checking/NameResolution.fs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1017,8 +1017,11 @@ let CheckForDirectReferenceToGeneratedType (tcref: TyconRef, genOk, m) =
10171017
let AddEntityForProvidedType (amap: Import.ImportMap, modref: ModuleOrNamespaceRef, resolutionEnvironment, st: Tainted<ProvidedType>, m) =
10181018
let importProvidedType t = Import.ImportProvidedType amap m t
10191019
let isSuppressRelocate = amap.g.isInteractive || st.PUntaint((fun st -> st.IsSuppressRelocate), m)
1020-
let tycon = Construct.NewProvidedTycon(resolutionEnvironment, st, importProvidedType, isSuppressRelocate, m)
1021-
modref.ModuleOrNamespaceType.AddProvidedTypeEntity tycon
1020+
let providedTypeName = st.PUntaint((fun st -> st.Name), m)
1021+
let tycon =
1022+
modref.ModuleOrNamespaceType.GetOrInternProvidedEntity(
1023+
providedTypeName,
1024+
(fun () -> Construct.NewProvidedTycon(resolutionEnvironment, st, importProvidedType, isSuppressRelocate, m)))
10221025
let tcref = modref.NestedTyconRef tycon
10231026
System.Diagnostics.Debug.Assert(modref.TryDeref.IsSome)
10241027
tcref

src/Compiler/Driver/CompilerImports.fs

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1572,7 +1572,11 @@ and [<Sealed>] TcImports
15721572
member _.ProviderGeneratedTypeRoots =
15731573
tciLock.AcquireLock(fun tcitok ->
15741574
RequireTcImportsLock(tcitok, generatedTypeRoots)
1575-
generatedTypeRoots.Values |> Seq.sortBy fst |> Seq.map snd |> Seq.toList)
1575+
// Sort by qualified name so the emitted set is deterministic regardless of parallel insertion order.
1576+
generatedTypeRoots.Values
1577+
|> Seq.map snd
1578+
|> Seq.sortBy (fun (ProviderGeneratedType(_, ilTyRef, _)) -> ilTyRef.QualifiedName)
1579+
|> Seq.toList)
15761580
#endif
15771581

15781582
member private tcImports.AttachDisposeAction action =
@@ -1796,10 +1800,13 @@ and [<Sealed>] TcImports
17961800
let isSuppressRelocate =
17971801
tcConfig.isInteractive || st.PUntaint((fun st -> st.IsSuppressRelocate), m)
17981802

1799-
let newEntity =
1800-
Construct.NewProvidedTycon(typeProviderEnvironment, st, importProvidedType, isSuppressRelocate, m)
1803+
let providedTypeName = st.PUntaint((fun st -> st.Name), m)
18011804

1802-
entity.ModuleOrNamespaceType.AddProvidedTypeEntity newEntity
1805+
entity.ModuleOrNamespaceType.GetOrInternProvidedEntity(
1806+
providedTypeName,
1807+
(fun () -> Construct.NewProvidedTycon(typeProviderEnvironment, st, importProvidedType, isSuppressRelocate, m))
1808+
)
1809+
|> ignore
18031810
| None -> ()
18041811

18051812
entity.entity_tycon_repr <-
@@ -1860,14 +1867,17 @@ and [<Sealed>] TcImports
18601867
let name = AssemblyName.GetAssemblyName(resolution.resolvedPath)
18611868
!!name.Version
18621869

1863-
// Note, this only captures systemRuntimeContainsTypeRef (which captures tcImportsWeak, using name tcImports)
18641870
let systemRuntimeContainsType =
18651871
let tcImports = tcImportsWeak
18661872

1867-
// The name of this captured value must not change, see comments on TcImportsWeakFacade above
1868-
assert (nameof tcImports = "tcImports")
1869-
1870-
let mutable systemRuntimeContainsTypeRef = tcImports.SystemRuntimeContainsType
1873+
// The TypeProvider SDK reflects over this closure and requires a captured field literally
1874+
// named `tcImports` (see comments on TcImportsWeakFacade above). Capture it through an
1875+
// explicit lambda rather than passing the method group `tcImports.SystemRuntimeContainsType`:
1876+
// a method-group value names its captured receiver an optimization-dependent synthesized
1877+
// name (e.g. `objectArg` under Debug/--optimize-), which breaks the SDK. An explicit lambda
1878+
// captures the local under its own name `tcImports` in every configuration.
1879+
let mutable systemRuntimeContainsTypeRef =
1880+
fun typeName -> tcImports.SystemRuntimeContainsType typeName
18711881

18721882
// When the tcImports is disposed the systemRuntimeContainsTypeRef thunk is replaced
18731883
// with one raising an exception.

src/Compiler/TypedTree/TypeProviders.fs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1443,9 +1443,10 @@ type ProviderGeneratedType = ProviderGeneratedType of ilOrigTyRef: ILTypeRef * i
14431443
/// The table of information recording remappings from type names in the provided assembly to type
14441444
/// names in the statically linked, embedded assembly, plus what types are nested in side what types.
14451445
type ProvidedAssemblyStaticLinkingMap =
1446-
{ ILTypeMap: Dictionary<ILTypeRef, ILTypeRef> }
1446+
// graph-based checking can embed generated types from one provider assembly in parallel
1447+
{ ILTypeMap: ConcurrentDictionary<ILTypeRef, ILTypeRef> }
14471448
static member CreateNew() =
1448-
{ ILTypeMap = Dictionary() }
1449+
{ ILTypeMap = ConcurrentDictionary() }
14491450

14501451
/// Check if this is a direct reference to a non-embedded generated type. This is not permitted at any name resolution.
14511452
/// We check by seeing if the type is absent from the remapping context.

src/Compiler/TypedTree/TypeProviders.fsi

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -485,7 +485,7 @@ type ProvidedAssemblyStaticLinkingMap =
485485
{
486486
/// The table of remappings from type names in the provided assembly to type
487487
/// names in the statically linked, embedded assembly.
488-
ILTypeMap: Dictionary<ILTypeRef, ILTypeRef>
488+
ILTypeMap: ConcurrentDictionary<ILTypeRef, ILTypeRef>
489489
}
490490

491491
/// Create a new static linking map, ready to populate with data.

src/Compiler/TypedTree/TypedTree.fs

Lines changed: 63 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ module internal rec FSharp.Compiler.TypedTree
55

66
open System
77
open System.Collections.Generic
8+
open System.Collections.Concurrent
89
open System.Collections.Immutable
910
open System.Diagnostics
1011
open Internal.Utilities.Collections
@@ -2020,27 +2021,41 @@ type ModuleOrNamespaceType(kind: ModuleOrNamespaceKind, vals: QueueList<Val>, en
20202021

20212022
/// Mutation used during compilation of FSharp.Core.dll
20222023
let mutable entities = entities
2024+
2025+
#if !NO_TYPEPROVIDERS
2026+
// One Entity per provided type even when linked concurrently from several files (graph-based checking).
2027+
let mutable providedEntitiesByMangledName: ConcurrentDictionary<string, Lazy<Entity>> | null = null
2028+
#endif
2029+
2030+
// Bumped (with release semantics) whenever 'entities' is mutated. The entity-derived lookup caches below
2031+
// are tagged with the version they were built from, so a reader on another graph-based-checking thread
2032+
// recomputes after a concurrent provided-type append instead of trusting a stale memo. Accessed via
2033+
// Interlocked/Volatile rather than [<VolatileField>] so its address can be taken for those intrinsics.
2034+
let mutable entitiesVersion = 0
20232035

20242036
// Lookup tables keyed the way various clients expect them to be keyed.
20252037
// We attach them here so we don't need to store lookup tables via any other technique.
20262038
//
20272039
// The type option ref is used because there are a few functions that treat these as first class values.
20282040
// We should probably change to 'mutable'.
20292041
//
2030-
// We do not need to lock this mutable state this it is only ever accessed from the compiler thread.
2042+
// We do not need to lock most of this mutable state since it is only ever accessed from the compiler thread.
2043+
// The exception is the four lookup tables invalidated by mutating 'entities' (provided-type linking can run
2044+
// on several graph-based-checking threads): those are read through 'cacheOptByrefByVersion' tagged with
2045+
// 'entitiesVersion', which stays coherent under concurrent appends without any lock.
20312046
let activePatternElemRefCache: NameMap<ActivePatternElemRef> option ref = ref None
20322047

20332048
let mutable modulesByDemangledNameCache: NameMap<ModuleOrNamespace> option = None
20342049

20352050
let mutable exconsByDemangledNameCache: NameMap<Tycon> option = None
20362051

2037-
let mutable tyconsByDemangledNameAndArityCache: LayeredMap<NameArityPair, Tycon> option = None
2052+
let mutable tyconsByDemangledNameAndArityCache: (int * LayeredMap<NameArityPair, Tycon>) option = None
20382053

2039-
let mutable tyconsByAccessNamesCache: LayeredMultiMap<string, Tycon> option = None
2054+
let mutable tyconsByAccessNamesCache: (int * LayeredMultiMap<string, Tycon>) option = None
20402055

2041-
let mutable tyconsByMangledNameCache: NameMap<Tycon> option = None
2056+
let mutable tyconsByMangledNameCache: (int * NameMap<Tycon>) option = None
20422057

2043-
let mutable allEntitiesByMangledNameCache: NameMap<Entity> option = None
2058+
let mutable allEntitiesByMangledNameCache: (int * NameMap<Entity>) option = None
20442059

20452060
let mutable allValsAndMembersByPartialLinkageKeyCache: MultiMap<ValLinkagePartialKey, Val> option = None
20462061

@@ -2063,15 +2078,34 @@ type ModuleOrNamespaceType(kind: ModuleOrNamespaceKind, vals: QueueList<Val>, en
20632078
entities <- QueueList.appendOne entities modul
20642079
modulesByDemangledNameCache <- None
20652080
allEntitiesByMangledNameCache <- None
2081+
System.Threading.Interlocked.Increment(&entitiesVersion) |> ignore
20662082

20672083
#if !NO_TYPEPROVIDERS
20682084
/// Mutation used in hosting scenarios to hold the hosted types in this module or namespace
2069-
member mtyp.AddProvidedTypeEntity(entity: Entity) =
2070-
entities <- QueueList.appendOne entities entity
2071-
tyconsByMangledNameCache <- None
2072-
tyconsByDemangledNameAndArityCache <- None
2073-
tyconsByAccessNamesCache <- None
2074-
allEntitiesByMangledNameCache <- None
2085+
member _.AddProvidedTypeEntity(entity: Entity) =
2086+
// Several graph-based-checking threads may link provided types into this module at once, so append
2087+
// atomically with a CAS loop. Bump 'entitiesVersion' last (release): a reader observing the new version
2088+
// is guaranteed to also see 'entity' in 'entities', so the version-stamped lookup caches recompute and
2089+
// never strand a table missing it. The caches need no explicit invalidation - the version bump does it.
2090+
let rec append () =
2091+
let current = entities
2092+
let updated = QueueList.appendOne current entity
2093+
if not (obj.ReferenceEquals(System.Threading.Interlocked.CompareExchange(&entities, updated, current), current)) then
2094+
append ()
2095+
append ()
2096+
System.Threading.Interlocked.Increment(&entitiesVersion) |> ignore
2097+
2098+
/// Interns a provided-type entity by mangled name; callers must use the returned entity.
2099+
member mtyp.GetOrInternProvidedEntity(mangledName: string, create: unit -> Entity) : Entity =
2100+
let table =
2101+
match providedEntitiesByMangledName with
2102+
| null ->
2103+
let created = ConcurrentDictionary<string, Lazy<Entity>>()
2104+
match System.Threading.Interlocked.CompareExchange(&providedEntitiesByMangledName, created, null) with
2105+
| null -> created
2106+
| existing -> existing
2107+
| existing -> existing
2108+
table.GetOrAdd(mangledName, fun _ -> lazy (let entity = create () in mtyp.AddProvidedTypeEntity entity; entity)).Value
20752109
#endif
20762110

20772111
/// Return a new module or namespace type with an entity added.
@@ -2101,31 +2135,35 @@ type ModuleOrNamespaceType(kind: ModuleOrNamespaceKind, vals: QueueList<Val>, en
21012135
/// table is indexed by both name and generic arity. This means that for generic
21022136
/// types "List`1", the entry (List, 1) will be present.
21032137
member mtyp.TypesByDemangledNameAndArity =
2104-
cacheOptByref &tyconsByDemangledNameAndArityCache (fun () ->
2138+
let version = System.Threading.Volatile.Read(&entitiesVersion)
2139+
cacheOptByrefByVersion version &tyconsByDemangledNameAndArityCache (fun () ->
21052140
LayeredMap.Empty.AddMany( mtyp.TypeAndExceptionDefinitions |> List.map (fun (tc: Tycon) -> Construct.KeyTyconByDecodedName tc.LogicalName tc) |> List.toArray))
21062141

21072142
/// Get a table of types defined within this module, namespace or type. The
21082143
/// table is indexed by both name and, for generic types, also by mangled name.
21092144
member mtyp.TypesByAccessNames =
2110-
cacheOptByref &tyconsByAccessNamesCache (fun () ->
2145+
let version = System.Threading.Volatile.Read(&entitiesVersion)
2146+
cacheOptByrefByVersion version &tyconsByAccessNamesCache (fun () ->
21112147
LayeredMultiMap.Empty.AddMany (mtyp.TypeAndExceptionDefinitions |> List.toArray |> Array.collect (fun (tc: Tycon) -> Construct.KeyTyconByAccessNames tc.LogicalName tc)))
21122148

21132149
// REVIEW: we can remove this lookup and use AllEntitiesByMangledName instead?
21142150
member mtyp.TypesByMangledName =
21152151
let addTyconByMangledName (x: Tycon) tab = NameMap.add x.LogicalName x tab
2116-
cacheOptByref &tyconsByMangledNameCache (fun () ->
2152+
let version = System.Threading.Volatile.Read(&entitiesVersion)
2153+
cacheOptByrefByVersion version &tyconsByMangledNameCache (fun () ->
21172154
List.foldBack addTyconByMangledName mtyp.TypeAndExceptionDefinitions Map.empty)
21182155

21192156
/// Get a table of entities indexed by both logical and compiled names
2120-
member _.AllEntitiesByCompiledAndLogicalMangledNames: NameMap<Entity> =
2157+
member mtyp.AllEntitiesByCompiledAndLogicalMangledNames: NameMap<Entity> =
21212158
let addEntityByMangledName (x: Entity) tab =
21222159
let name1 = x.LogicalName
21232160
let name2 = x.CompiledName
21242161
let tab = NameMap.add name1 x tab
21252162
if name1 = name2 then tab
21262163
else NameMap.add name2 x tab
21272164

2128-
cacheOptByref &allEntitiesByMangledNameCache (fun () ->
2165+
let version = System.Threading.Volatile.Read(&entitiesVersion)
2166+
cacheOptByrefByVersion version &allEntitiesByMangledNameCache (fun () ->
21292167
QueueList.foldBack addEntityByMangledName entities Map.empty)
21302168

21312169
/// Get a table of entities indexed by both logical name
@@ -3524,10 +3562,12 @@ type NonLocalEntityRef =
35243562
match st.PApply((fun st -> st.GetNestedType path[i]), m) with
35253563
| Tainted.Null -> ValueNone
35263564
| Tainted.NonNull st ->
3527-
let newEntity = Construct.NewProvidedTycon(resolutionEnvironment, st, ccu.ImportProvidedType, false, m)
3528-
parentEntity.ModuleOrNamespaceType.AddProvidedTypeEntity newEntity
3529-
if i = path.Length-1 then ValueSome newEntity
3530-
else tryResolveNestedTypeOf(newEntity, resolutionEnvironment, st, i+1)
3565+
let canonicalEntity =
3566+
parentEntity.ModuleOrNamespaceType.GetOrInternProvidedEntity(
3567+
path[i],
3568+
(fun () -> Construct.NewProvidedTycon(resolutionEnvironment, st, ccu.ImportProvidedType, false, m)))
3569+
if i = path.Length-1 then ValueSome canonicalEntity
3570+
else tryResolveNestedTypeOf(canonicalEntity, resolutionEnvironment, st, i+1)
35313571

35323572
tryResolveNestedTypeOf(entity, resolutionEnvironment, st, i)
35333573

@@ -3570,9 +3610,9 @@ type NonLocalEntityRef =
35703610
// Note: this is similar to code in CompileOps.fs
35713611
let rec injectNamespacesFromIToJ (entity: Entity) k =
35723612
if k = j then
3573-
let newEntity = Construct.NewProvidedTycon(resolutionEnvironment, st, ccu.ImportProvidedType, false, m)
3574-
entity.ModuleOrNamespaceType.AddProvidedTypeEntity newEntity
3575-
newEntity
3613+
entity.ModuleOrNamespaceType.GetOrInternProvidedEntity(
3614+
path[j],
3615+
(fun () -> Construct.NewProvidedTycon(resolutionEnvironment, st, ccu.ImportProvidedType, false, m)))
35763616
else
35773617
let cpath = entity.CompilationPath.NestedCompPath entity.LogicalName (ModuleOrNamespaceKind.Namespace false)
35783618
let newEntity =

src/Compiler/TypedTree/TypedTree.fsi

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1386,6 +1386,10 @@ type ModuleOrNamespaceType =
13861386
#if !NO_TYPEPROVIDERS
13871387
/// Mutation used in hosting scenarios to hold the hosted types in this module or namespace
13881388
member AddProvidedTypeEntity: entity: Entity -> unit
1389+
1390+
/// Interns a provided-type entity by mangled name so concurrent linking from multiple files yields one
1391+
/// Entity. The first caller's 'create' wins; callers must use the returned entity.
1392+
member GetOrInternProvidedEntity: mangledName: string * create: (unit -> Entity) -> Entity
13891393
#endif
13901394

13911395
/// Return a new module or namespace type with a value added.

0 commit comments

Comments
 (0)