From b14dccbf2b1f9a05808880906cbe1d1a823a1f04 Mon Sep 17 00:00:00 2001 From: Jakub Majocha <1760221+majocha@users.noreply.github.com> Date: Thu, 16 Oct 2025 12:59:28 +0200 Subject: [PATCH 1/4] Add a failsafe in case of infinite types --- src/Compiler/Utilities/TypeHashing.fs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Compiler/Utilities/TypeHashing.fs b/src/Compiler/Utilities/TypeHashing.fs index af536a9d2b..f08fee1e46 100644 --- a/src/Compiler/Utilities/TypeHashing.fs +++ b/src/Compiler/Utilities/TypeHashing.fs @@ -401,7 +401,7 @@ module StructuralUtilities = | MeasureRational of int * int | NeverEqual of never: NeverEqual - type TypeStructure = TypeStructure of ImmutableArray + type TypeStructure = TypeStructure of TypeToken[] let inline toNullnessToken (n: Nullness) = match n.TryEvaluate() with @@ -464,6 +464,11 @@ module StructuralUtilities = | TType_measure m -> yield! accumulateMeasure m } + // If the sequence got too long, just drop it. + let private getTokens tokens = + let result = tokens |> Seq.truncate 256 |> Array.ofSeq + if result.Length = 256 then Array.singleton (TypeToken.NeverEqual NeverEqual.Singleton) else result + /// Get the full structure of a type as a sequence of tokens, suitable for equality let getTypeStructure = - Extras.WeakMap.getOrCreate (fun ty -> accumulateTType ty |> ImmutableArray.ofSeq |> TypeStructure) + Extras.WeakMap.getOrCreate (fun ty -> accumulateTType ty |> getTokens |> TypeStructure) From a93b84c7ffb0c4cc2ce8f1ae5064ab7c1b11a3a9 Mon Sep 17 00:00:00 2001 From: Jakub Majocha <1760221+majocha@users.noreply.github.com> Date: Thu, 16 Oct 2025 13:33:22 +0200 Subject: [PATCH 2/4] do not cache when invalid keys --- src/Compiler/Checking/TypeRelations.fs | 14 ++++++++++---- src/Compiler/Utilities/TypeHashing.fs | 8 ++++++-- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/Compiler/Checking/TypeRelations.fs b/src/Compiler/Checking/TypeRelations.fs index 27274f7ebd..5801d7b702 100644 --- a/src/Compiler/Checking/TypeRelations.fs +++ b/src/Compiler/Checking/TypeRelations.fs @@ -28,8 +28,12 @@ type CanCoerce = [] type TTypeCacheKey = | TTypeCacheKey of TypeStructure * TypeStructure * CanCoerce - static member FromStrippedTypes(ty1, ty2, canCoerce) = - TTypeCacheKey(getTypeStructure ty1, getTypeStructure ty2, canCoerce) + static member TryGetFromStrippedTypes(ty1, ty2, canCoerce) = + let t1, t2 = getTypeStructure ty1, getTypeStructure ty2 + if t1 = InvalidTypeStructure || t2 = InvalidTypeStructure then + ValueNone + else + ValueSome (TTypeCacheKey(t1, t2, canCoerce)) let getTypeSubsumptionCache = let factory (g: TcGlobals) = @@ -157,8 +161,10 @@ let rec TypeFeasiblySubsumesType ndeep (g: TcGlobals) (amap: ImportMap) m (ty1: List.exists (TypeFeasiblySubsumesType (ndeep + 1) g amap m ty1 NoCoerce) interfaces if g.langVersion.SupportsFeature LanguageFeature.UseTypeSubsumptionCache then - let key = TTypeCacheKey.FromStrippedTypes(ty1, ty2, canCoerce) - (getTypeSubsumptionCache g).GetOrAdd(key, fun _ -> checkSubsumes ty1 ty2) + match TTypeCacheKey.TryGetFromStrippedTypes(ty1, ty2, canCoerce) with + | ValueSome key -> + (getTypeSubsumptionCache g).GetOrAdd(key, fun _ -> checkSubsumes ty1 ty2) + | _ -> checkSubsumes ty1 ty2 else checkSubsumes ty1 ty2 diff --git a/src/Compiler/Utilities/TypeHashing.fs b/src/Compiler/Utilities/TypeHashing.fs index f08fee1e46..db1bce767e 100644 --- a/src/Compiler/Utilities/TypeHashing.fs +++ b/src/Compiler/Utilities/TypeHashing.fs @@ -464,10 +464,14 @@ module StructuralUtilities = | TType_measure m -> yield! accumulateMeasure m } - // If the sequence got too long, just drop it. + let private neverEqual = [| TypeToken.NeverEqual NeverEqual.Singleton |] + + let InvalidTypeStructure = TypeStructure neverEqual + + // If the sequence got too long, just drop it, we could be dealing with an infinite type. let private getTokens tokens = let result = tokens |> Seq.truncate 256 |> Array.ofSeq - if result.Length = 256 then Array.singleton (TypeToken.NeverEqual NeverEqual.Singleton) else result + if result.Length = 256 then neverEqual else result /// Get the full structure of a type as a sequence of tokens, suitable for equality let getTypeStructure = From a2be83db016d6df04e28e9eed11c926f39d77ce4 Mon Sep 17 00:00:00 2001 From: Jakub Majocha <1760221+majocha@users.noreply.github.com> Date: Thu, 16 Oct 2025 15:15:32 +0200 Subject: [PATCH 3/4] refactor --- src/Compiler/Checking/TypeRelations.fs | 2 +- src/Compiler/Utilities/TypeHashing.fs | 20 +++++++++++--------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/Compiler/Checking/TypeRelations.fs b/src/Compiler/Checking/TypeRelations.fs index 5801d7b702..cb71ea87de 100644 --- a/src/Compiler/Checking/TypeRelations.fs +++ b/src/Compiler/Checking/TypeRelations.fs @@ -30,7 +30,7 @@ type TTypeCacheKey = | TTypeCacheKey of TypeStructure * TypeStructure * CanCoerce static member TryGetFromStrippedTypes(ty1, ty2, canCoerce) = let t1, t2 = getTypeStructure ty1, getTypeStructure ty2 - if t1 = InvalidTypeStructure || t2 = InvalidTypeStructure then + if t1.IsPossiblyInfinite || t2.IsPossiblyInfinite then ValueNone else ValueSome (TTypeCacheKey(t1, t2, canCoerce)) diff --git a/src/Compiler/Utilities/TypeHashing.fs b/src/Compiler/Utilities/TypeHashing.fs index db1bce767e..8e3752d5d3 100644 --- a/src/Compiler/Utilities/TypeHashing.fs +++ b/src/Compiler/Utilities/TypeHashing.fs @@ -401,7 +401,9 @@ module StructuralUtilities = | MeasureRational of int * int | NeverEqual of never: NeverEqual - type TypeStructure = TypeStructure of TypeToken[] + type TypeStructure = + | TypeStructure of TypeToken[] + | PossiblyInfinite of never: NeverEqual let inline toNullnessToken (n: Nullness) = match n.TryEvaluate() with @@ -464,15 +466,15 @@ module StructuralUtilities = | TType_measure m -> yield! accumulateMeasure m } - let private neverEqual = [| TypeToken.NeverEqual NeverEqual.Singleton |] - - let InvalidTypeStructure = TypeStructure neverEqual - // If the sequence got too long, just drop it, we could be dealing with an infinite type. - let private getTokens tokens = - let result = tokens |> Seq.truncate 256 |> Array.ofSeq - if result.Length = 256 then neverEqual else result + let private toTypeStructure tokens = + let tokens = tokens |> Seq.truncate 256 |> Array.ofSeq + + if tokens.Length = 256 then + PossiblyInfinite NeverEqual.Singleton + else + TypeStructure tokens /// Get the full structure of a type as a sequence of tokens, suitable for equality let getTypeStructure = - Extras.WeakMap.getOrCreate (fun ty -> accumulateTType ty |> getTokens |> TypeStructure) + Extras.WeakMap.getOrCreate (fun ty -> accumulateTType ty |> toTypeStructure) From cf7ffb6d5749fe694ead6daeef9dc5ad65fae8bb Mon Sep 17 00:00:00 2001 From: Jakub Majocha <1760221+majocha@users.noreply.github.com> Date: Thu, 16 Oct 2025 16:21:22 +0200 Subject: [PATCH 4/4] rn --- docs/release-notes/.FSharp.Compiler.Service/11.0.0.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/release-notes/.FSharp.Compiler.Service/11.0.0.md b/docs/release-notes/.FSharp.Compiler.Service/11.0.0.md index 0469b0575f..823740aa44 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/11.0.0.md +++ b/docs/release-notes/.FSharp.Compiler.Service/11.0.0.md @@ -3,6 +3,7 @@ * Scripts: Fix resolving the dotnet host path when an SDK directory is specified. ([PR #18960](https://github.com/dotnet/fsharp/pull/18960)) * Fix excessive StackGuard thread jumping ([PR #18971](https://github.com/dotnet/fsharp/pull/18971)) * Fix name is bound multiple times is not reported in 'as' pattern ([PR #18984](https://github.com/dotnet/fsharp/pull/18984)) +* Type relations cache: handle potentially "infinite" types ([PR #19010](https://github.com/dotnet/fsharp/pull/19010)) ### Added