diff --git a/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md b/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md index 38dd2d65b0a..e23722c0977 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md +++ b/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md @@ -166,6 +166,7 @@ ### Improved +* IL: cache C# extension methods per CCU ([PR #20256](https://github.com/dotnet/fsharp/pull/20256)) * Nullness warning FS3261 on dotted method or property access (e.g. `x.Member`) now underlines the receiver expression and includes the member name and (when known) the binding name in the message. ([Issue #19658](https://github.com/dotnet/fsharp/issues/19658), [PR #19814](https://github.com/dotnet/fsharp/pull/19814)) * Direct delegate construction ([PR ##19993](https://github.com/dotnet/fsharp/pull/19993)) diff --git a/src/Compiler/Checking/NameResolution.fs b/src/Compiler/Checking/NameResolution.fs index b93d9e416b2..856519cd7ff 100644 --- a/src/Compiler/Checking/NameResolution.fs +++ b/src/Compiler/Checking/NameResolution.fs @@ -587,20 +587,9 @@ let GetTyconRefForExtensionMembers minfo (deref: Entity) amap m g = None /// Get the info for all the .NET-style extension members listed as static members in the type. -let private GetCSharpStyleIndexedExtensionMembersForTyconRef (amap: Import.ImportMap) m (tcrefOfStaticClass: TyconRef) = +let private ComputeCSharpStyleExtensionMembers (amap: Import.ImportMap) m (tcrefOfStaticClass: TyconRef) (importFailed: bool ref) = let g = amap.g - - let isApplicable = - IsTyconRefUsedForCSharpStyleExtensionMembers g tcrefOfStaticClass || - - g.langVersion.SupportsFeature(LanguageFeature.CSharpExtensionAttributeNotRequired) && - tcrefOfStaticClass.IsLocalRef && - not tcrefOfStaticClass.IsTypeAbbrev - - if not isApplicable then [] else - let ty = generalizedTyconRef g tcrefOfStaticClass - let pri = NextExtensionMethodPriority() let methods = protectAssemblyExploration [] @@ -608,7 +597,6 @@ let private GetCSharpStyleIndexedExtensionMembersForTyconRef (amap: Import.Impor [ for minfo in methods do if IsMethInfoPlainCSharpStyleExtensionMember g m true minfo then - let ilExtMem = ILExtMem (tcrefOfStaticClass, minfo, pri) // The results are indexed by the TyconRef of the first 'this' argument, if any. // So we need to go and crack the type of the 'this' argument. // @@ -620,9 +608,49 @@ let private GetCSharpStyleIndexedExtensionMembersForTyconRef (amap: Import.Impor // methods for tuple occur in C# code) let thisTyconRef = GetTyconRefForExtensionMembers minfo tcrefOfStaticClass.Deref amap m g match thisTyconRef with - | None -> () - | Some (Some tcref) -> yield Choice1Of2(tcref, ilExtMem) - | Some None -> yield Choice2Of2 ilExtMem ] + | None -> importFailed.Value <- true + | Some thisTyconRefOpt -> yield (thisTyconRefOpt, minfo) ] + +let private GetCSharpStyleIndexedExtensionMembersForTyconRef (amap: Import.ImportMap) m (tcrefOfStaticClass: TyconRef) = + let g = amap.g + + let isApplicable = + IsTyconRefUsedForCSharpStyleExtensionMembers g tcrefOfStaticClass || + + g.langVersion.SupportsFeature(LanguageFeature.CSharpExtensionAttributeNotRequired) && + tcrefOfStaticClass.IsLocalRef && + not tcrefOfStaticClass.IsTypeAbbrev + + // Checked before touching the CCU or its cache, so a non-extension class costs nothing extra. + if not isApplicable then [] else + + let shape = + // A local class is still gaining members while its own file is checked; only an imported one is + // immutable enough to share. The cache lives on the CCU, so it goes when the project does. + if tcrefOfStaticClass.IsLocalRef then + ComputeCSharpStyleExtensionMembers amap m tcrefOfStaticClass (ref false) + else + let cache = tcrefOfStaticClass.nlr.Ccu.Deref.CSharpStyleExtensionMembersCache + match cache.TryGetValue tcrefOfStaticClass.Stamp with + | true, shape -> shape :?> (TyconRef option * MethInfo) list + | _ -> + let importFailed = ref false + let shape = ComputeCSharpStyleExtensionMembers amap m tcrefOfStaticClass importFailed + + if not importFailed.Value then + cache.TryAdd(tcrefOfStaticClass.Stamp, (shape :> obj)) |> ignore + + shape + + if List.isEmpty shape then [] else + + let pri = NextExtensionMethodPriority() + + [ for thisTyconRefOpt, minfo in shape do + let ilExtMem = ILExtMem (tcrefOfStaticClass, minfo, pri) + match thisTyconRefOpt with + | Some tcref -> yield Choice1Of2(tcref, ilExtMem) + | None -> yield Choice2Of2 ilExtMem ] /// Query the declared properties of a type (including inherited properties) let IntrinsicPropInfosOfTypeInScope (infoReader: InfoReader) optFilter ad findFlag m ty = diff --git a/src/Compiler/Checking/import.fs b/src/Compiler/Checking/import.fs index fb8c1efee05..e226b411458 100644 --- a/src/Compiler/Checking/import.fs +++ b/src/Compiler/Checking/import.fs @@ -875,6 +875,7 @@ let ImportILAssembly(amap: unit -> ImportMap, m, auxModuleLoader, xmlDocInfoLoad MemberSignatureEquality= (fun ty1 ty2 -> typeEquivAux EraseAll (amap()).g ty1 ty2) TryGetILModuleDef = (fun () -> Some ilModule) TypeForwarders = forwarders + CSharpStyleExtensionMembersCache = ConcurrentDictionary(1, 0) XmlDocumentationInfo = match xmlDocInfoLoader, fileName with | Some xmlDocInfoLoader, Some fileName -> xmlDocInfoLoader.TryLoad(fileName) diff --git a/src/Compiler/Driver/CompilerImports.fs b/src/Compiler/Driver/CompilerImports.fs index 68edbca6bef..e9e5c2d4206 100644 --- a/src/Compiler/Driver/CompilerImports.fs +++ b/src/Compiler/Driver/CompilerImports.fs @@ -5,6 +5,7 @@ module internal FSharp.Compiler.CompilerImports open System +open System.Collections.Concurrent open System.Collections.Generic open System.Diagnostics open System.IO @@ -1532,6 +1533,7 @@ and [] TcImports ImportProvidedType = (fun ty -> ImportProvidedType (tcImports.GetImportMap()) m ty) TryGetILModuleDef = (fun () -> Some ilModule) TypeForwarders = CcuTypeForwarderTable.Empty + CSharpStyleExtensionMembersCache = ConcurrentDictionary(1, 0) XmlDocumentationInfo = match tcConfig.xmlDocInfoLoader with | Some xmlDocInfoLoader -> xmlDocInfoLoader.TryLoad(fileName) @@ -2156,6 +2158,7 @@ and [] TcImports UsesFSharp20PlusQuotations = minfo.usesQuotations MemberSignatureEquality = (fun ty1 ty2 -> typeEquivAux EraseAll (tcImports.GetTcGlobals()) ty1 ty2) TypeForwarders = ImportILAssemblyTypeForwarders(tcImports.GetImportMap, m, ilModule.GetRawTypeForwarders()) + CSharpStyleExtensionMembersCache = ConcurrentDictionary(1, 0) #if !NO_TYPEPROVIDERS XmlDocumentationInfo = match tcConfig.xmlDocInfoLoader with diff --git a/src/Compiler/Driver/ParseAndCheckInputs.fs b/src/Compiler/Driver/ParseAndCheckInputs.fs index 17a316b7553..954809f9488 100644 --- a/src/Compiler/Driver/ParseAndCheckInputs.fs +++ b/src/Compiler/Driver/ParseAndCheckInputs.fs @@ -6,6 +6,7 @@ module internal FSharp.Compiler.ParseAndCheckInputs open System open System.IO open System.Threading +open System.Collections.Concurrent open System.Collections.Generic open FSharp.Compiler.Parser @@ -1098,6 +1099,7 @@ let GetInitialTcState (m, ccuName, tcConfig: TcConfig, tcGlobals, tcImports: TcI Contents = ccuContents MemberSignatureEquality = typeEquivAux EraseAll tcGlobals TypeForwarders = CcuTypeForwarderTable.Empty + CSharpStyleExtensionMembersCache = ConcurrentDictionary(1, 0) XmlDocumentationInfo = None } diff --git a/src/Compiler/TypedTree/TypedTree.fs b/src/Compiler/TypedTree/TypedTree.fs index 236a4a9b798..5e3ef4d9843 100644 --- a/src/Compiler/TypedTree/TypedTree.fs +++ b/src/Compiler/TypedTree/TypedTree.fs @@ -5888,7 +5888,9 @@ type CcuData = /// The table of .NET CLI type forwarders for this assembly TypeForwarders: CcuTypeForwarderTable - + + CSharpStyleExtensionMembersCache: ConcurrentDictionary + XmlDocumentationInfo: XmlDocumentationInfo option } [] diff --git a/src/Compiler/TypedTree/TypedTree.fsi b/src/Compiler/TypedTree/TypedTree.fsi index 98c4ab0e840..6163400efe2 100644 --- a/src/Compiler/TypedTree/TypedTree.fsi +++ b/src/Compiler/TypedTree/TypedTree.fsi @@ -3,6 +3,7 @@ module internal rec FSharp.Compiler.TypedTree open System open System.Diagnostics +open System.Collections.Concurrent open System.Collections.Generic open System.Collections.Immutable open Internal.Utilities.Collections @@ -4228,6 +4229,10 @@ type CcuData = /// The table of .NET CLI type forwarders for this assembly TypeForwarders: CcuTypeForwarderTable + + /// C#-style extension members of this assembly's static classes, keyed by static class stamp. + /// Typed as obj because MethInfo is declared after this file. + CSharpStyleExtensionMembersCache: ConcurrentDictionary XmlDocumentationInfo: XmlDocumentationInfo option }