From 682384b52c739a015c69c0901f4448822ab684db Mon Sep 17 00:00:00 2001 From: evgTSV Date: Wed, 1 Apr 2026 00:08:12 +0500 Subject: [PATCH 01/12] [NameResolution] Fix accessibility and type-matching for pattern-based extension lookups (CE, Dispose, etc.) --- src/Compiler/Checking/NameResolution.fs | 35 ++++++++++++++++++++----- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/src/Compiler/Checking/NameResolution.fs b/src/Compiler/Checking/NameResolution.fs index 55ab3c9219b..4ed07e463d0 100644 --- a/src/Compiler/Checking/NameResolution.fs +++ b/src/Compiler/Checking/NameResolution.fs @@ -727,7 +727,7 @@ let SelectMethInfosFromExtMembers (infoReader: InfoReader) optFilter apparentTy ] /// Query the available extension methods of a type (including extension methods for inherited types) -let ExtensionMethInfosOfTypeInScope (collectionSettings: ResultCollectionSettings) (infoReader: InfoReader) (nenv: NameResolutionEnv) optFilter isInstanceFilter m ty = +let ExtensionMethInfosOfTypeInScope (collectionSettings: ResultCollectionSettings) (infoReader: InfoReader) (nenv: NameResolutionEnv) ad optFilter isInstanceFilter m ty = let extMemsDangling = SelectMethInfosFromExtMembers infoReader optFilter ty m nenv.eUnindexedExtensionMembers if collectionSettings = ResultCollectionSettings.AtMostOneResult && not (isNil extMemsDangling) then extMemsDangling @@ -743,6 +743,27 @@ let ExtensionMethInfosOfTypeInScope (collectionSettings: ResultCollectionSetting | _ -> []) extMemsDangling @ extMemsFromHierarchy |> List.filter (fun minfo -> + let g = infoReader.g + let amap = infoReader.amap + + let isAccesible = AccessibilityLogic.IsMethInfoAccessible amap m ad minfo + + let isThisArgEq = + match minfo.GetObjArgTypes(amap, m, []) with + | thisTy :: _ -> + let t1 = stripTyEqnsWrtErasure EraseNone g thisTy + let t2 = stripTyEqnsWrtErasure EraseNone g ty + + match t1, t2 with + | TType_app (tc1, _, _), TType_app (tc2, _, _) -> + tyconRefEq g tc1 tc2 + | _ -> + false + | _ -> + false + + isAccesible && + isThisArgEq && match isInstanceFilter with | LookupIsInstance.Ambivalent -> true | LookupIsInstance.Yes -> minfo.IsInstance @@ -754,7 +775,7 @@ let AllMethInfosOfTypeInScope collectionSettings infoReader nenv optFilter ad fi if collectionSettings = ResultCollectionSettings.AtMostOneResult && not (isNil intrinsic) then intrinsic else - intrinsic @ ExtensionMethInfosOfTypeInScope collectionSettings infoReader nenv optFilter LookupIsInstance.Ambivalent m ty + intrinsic @ ExtensionMethInfosOfTypeInScope collectionSettings infoReader nenv ad optFilter LookupIsInstance.Ambivalent m ty //------------------------------------------------------------------------- // Helpers to do with building environments @@ -1184,7 +1205,7 @@ let rec AddStaticContentOfTypeToNameEnv (g:TcGlobals) (amap: Import.ImportMap) a [| // Extension methods yield! - ExtensionMethInfosOfTypeInScope ResultCollectionSettings.AllResults infoReader nenv None LookupIsInstance.No m ty + ExtensionMethInfosOfTypeInScope ResultCollectionSettings.AllResults infoReader nenv ad None LookupIsInstance.No m ty |> ChooseMethInfosForNameEnv g m ty // Extension properties @@ -2827,7 +2848,7 @@ let rec ResolveLongIdentInTypePrim (ncenv: NameResolver) nenv lookupKind (resInf | _ -> // lookup in-scope extension methods // to keep in sync with the same expression in `| Some(MethodItem msets) when isLookupExpr` below - match ExtensionMethInfosOfTypeInScope ResultCollectionSettings.AllResults ncenv.InfoReader nenv optFilter isInstanceFilter m ty with + match ExtensionMethInfosOfTypeInScope ResultCollectionSettings.AllResults ncenv.InfoReader nenv ad optFilter isInstanceFilter m ty with | [] -> success [resInfo, x, rest] | methods -> let extensionMethods = Item.MakeMethGroup(nm, methods) @@ -2841,7 +2862,7 @@ let rec ResolveLongIdentInTypePrim (ncenv: NameResolver) nenv lookupKind (resInf let minfos = msets |> ExcludeHiddenOfMethInfos g ncenv.amap m // fold the available extension members into the overload resolution - let extensionMethInfos = ExtensionMethInfosOfTypeInScope ResultCollectionSettings.AllResults ncenv.InfoReader nenv optFilter isInstanceFilter m ty + let extensionMethInfos = ExtensionMethInfosOfTypeInScope ResultCollectionSettings.AllResults ncenv.InfoReader nenv ad optFilter isInstanceFilter m ty success [resInfo, Item.MakeMethGroup (nm, minfos@extensionMethInfos), rest] @@ -2860,7 +2881,7 @@ let rec ResolveLongIdentInTypePrim (ncenv: NameResolver) nenv lookupKind (resInf if not (isNil pinfos) && isLookUpExpr then OneResult(success (resInfo, Item.Property (nm, pinfos, None), rest)) else - let minfos = ExtensionMethInfosOfTypeInScope ResultCollectionSettings.AllResults ncenv.InfoReader nenv optFilter isInstanceFilter m ty + let minfos = ExtensionMethInfosOfTypeInScope ResultCollectionSettings.AllResults ncenv.InfoReader nenv ad optFilter isInstanceFilter m ty if not (isNil minfos) && isLookUpExpr then success [resInfo, Item.MakeMethGroup (nm, minfos), rest] @@ -2898,7 +2919,7 @@ let rec ResolveLongIdentInTypePrim (ncenv: NameResolver) nenv lookupKind (resInf for p in ExtensionPropInfosOfTypeInScope ResultCollectionSettings.AllResults ncenv.InfoReader nenv None LookupIsInstance.Ambivalent ad m ty do addToBuffer p.PropertyName - for m in ExtensionMethInfosOfTypeInScope ResultCollectionSettings.AllResults ncenv.InfoReader nenv None LookupIsInstance.Ambivalent m ty do + for m in ExtensionMethInfosOfTypeInScope ResultCollectionSettings.AllResults ncenv.InfoReader nenv ad None LookupIsInstance.Ambivalent m ty do addToBuffer m.DisplayName for p in GetIntrinsicPropInfosOfType ncenv.InfoReader None ad AllowMultiIntfInstantiations.No findFlag m ty do From b2e38f07858537fe4837ee02e061de171a23b6db Mon Sep 17 00:00:00 2001 From: evgTSV Date: Wed, 1 Apr 2026 02:13:13 +0500 Subject: [PATCH 02/12] Add tests --- .../UseBindingsAndExtensionMembers.fs | 32 ++++++ .../CEExtensionMethodCapture.fs | 103 ++++++++++++++++++ .../FSharp.Compiler.ComponentTests.fsproj | 2 + 3 files changed, 137 insertions(+) create mode 100644 tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/UseBindings/UseBindingsAndExtensionMembers.fs create mode 100644 tests/FSharp.Compiler.ComponentTests/Conformance/Expressions/ComputationExpressions/CEExtensionMethodCapture.fs diff --git a/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/UseBindings/UseBindingsAndExtensionMembers.fs b/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/UseBindings/UseBindingsAndExtensionMembers.fs new file mode 100644 index 00000000000..03986a2668a --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/UseBindings/UseBindingsAndExtensionMembers.fs @@ -0,0 +1,32 @@ +module Conformance.Expressions.CEExtensionMethodCapture + +open Xunit +open FSharp.Test.Compiler + +[] +let ``Use binding doesn't capture an extension method with generic type``() = + FSharp """ + open System + open System.Runtime.CompilerServices + + type FooClass() = class end + + type Disposable() = + interface IDisposable with + member _.Dispose() = () + + [] + type PublicExtensions = + [] + static member inline Dispose(this: #FooClass) = + this + + let foo() = + use a = new Disposable() + () + + foo() + """ + |> asExe + |> compile + |> shouldSucceed \ No newline at end of file diff --git a/tests/FSharp.Compiler.ComponentTests/Conformance/Expressions/ComputationExpressions/CEExtensionMethodCapture.fs b/tests/FSharp.Compiler.ComponentTests/Conformance/Expressions/ComputationExpressions/CEExtensionMethodCapture.fs new file mode 100644 index 00000000000..2b42463330e --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/Conformance/Expressions/ComputationExpressions/CEExtensionMethodCapture.fs @@ -0,0 +1,103 @@ +module Conformance.Expressions.CEExtensionMethodCapture + +open Xunit +open FSharp.Test.Compiler + +[] +let ``CE doesn't capture an extension method beyond the access domain``() = + FSharp """ + open System.Runtime.CompilerServices + + type AsyncSeq<'T>(i: 'T) = + class + let l = [i] + member this.Data = l + end + + type AsyncSeqBuilder() = + member _.Yield(x: 'T) : AsyncSeq<'T> = + AsyncSeq(x) + + [] + type PrivateExtensions = + [] + static member inline private Run(this: AsyncSeqBuilder) = + this + + let asyncSeq = AsyncSeqBuilder() + + let xs : AsyncSeq = + asyncSeq { + yield 1 + } + """ + |> asExe + |> compile + |> shouldSucceed + +[] +let ``CE doesn't capture an extension method with generic type``() = + FSharp """ + open System.Runtime.CompilerServices + + type FooClass = class end + + type AsyncSeq<'T>(i: 'T) = + class + let l = [i] + member this.Data = l + end + + type AsyncSeqBuilder() = + member _.Yield(x: 'T) : AsyncSeq<'T> = + AsyncSeq(x) + + [] + type PrivateExtensions = + [] + static member inline Run(this: #FooClass) = + this + + let asyncSeq = AsyncSeqBuilder() + + let xs : AsyncSeq = + asyncSeq { + yield 1 + } + """ + |> asExe + |> compile + |> shouldSucceed + +// Deliberately trigger an error to ensure that a method is captured +[] +let ``CE captures a public extension method and procudes an error due to invalid args``() = + FSharp """ + open System.Runtime.CompilerServices + + type AsyncSeq<'T>(i: 'T) = + class + let l = [i] + member this.Data = l + end + + type AsyncSeqBuilder() = + member _.Yield(x: 'T) : AsyncSeq<'T> = + AsyncSeq(x) + + [] + type PublicExtensions = + [] + static member inline Run(this: AsyncSeqBuilder, invalidArg: string) = + this + + let asyncSeq = AsyncSeqBuilder() + + let xs : AsyncSeq = + asyncSeq { + yield 1 + } + """ + |> asExe + |> compile + |> shouldFail \ No newline at end of file diff --git a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj index f5d1048408e..2f07460ca6a 100644 --- a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj +++ b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj @@ -76,6 +76,7 @@ + @@ -85,6 +86,7 @@ + From 170baf19fa7df378d6f20beb7d10a20c5670d641 Mon Sep 17 00:00:00 2001 From: evgTSV Date: Wed, 1 Apr 2026 02:21:00 +0500 Subject: [PATCH 03/12] Add the entry in the changeloh --- docs/release-notes/.FSharp.Compiler.Service/11.0.100.md | 1 + 1 file changed, 1 insertion(+) 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 d5c2087765e..dca26a94e3f 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md +++ b/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md @@ -17,6 +17,7 @@ * Fix `YieldFromFinal`/`ReturnFromFinal` being incorrectly called in non-tail positions (`for`, `use`, `use!`, `try/with` handler). ([Issue #19402](https://github.com/dotnet/fsharp/issues/19402), [PR #19403](https://github.com/dotnet/fsharp/pull/19403)) * Fixed how the source ranges of warn directives are reported (as trivia) in the parser output (by not reporting leading spaces). ([Issue #19405](https://github.com/dotnet/fsharp/issues/19405), [PR #19408]((https://github.com/dotnet/fsharp/pull/19408))) * Fix UoM value type `ToString()` returning garbage values when `--checknulls+` is enabled, caused by double address-taking in codegen. ([Issue #19435](https://github.com/dotnet/fsharp/issues/19435), [PR #19440](https://github.com/dotnet/fsharp/pull/19440)) +* Fix accessibility and type-matching for CE and 'use' extension method lookups. ([Issue #19349](https://github.com/dotnet/fsharp/issues/19349), [PR #19536](https://github.com/dotnet/fsharp/pull/19536)) ### Added From a4d3388e8be41ea7a4e31e2b7346ce5190e54bbf Mon Sep 17 00:00:00 2001 From: evgTSV Date: Wed, 1 Apr 2026 17:58:18 +0500 Subject: [PATCH 04/12] Fix module name --- .../UseBindings/UseBindingsAndExtensionMembers.fs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/UseBindings/UseBindingsAndExtensionMembers.fs b/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/UseBindings/UseBindingsAndExtensionMembers.fs index 03986a2668a..97315ef4cdc 100644 --- a/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/UseBindings/UseBindingsAndExtensionMembers.fs +++ b/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/UseBindings/UseBindingsAndExtensionMembers.fs @@ -1,4 +1,4 @@ -module Conformance.Expressions.CEExtensionMethodCapture +module Conformance.BasicGrammarElements.UseBindExtensionMethodCapture open Xunit open FSharp.Test.Compiler From f37ce5b3e22e4032faa17cf7530a981e460716f8 Mon Sep 17 00:00:00 2001 From: evgTSV Date: Wed, 1 Apr 2026 22:46:33 +0500 Subject: [PATCH 05/12] Apply access/type-eq filter to only unindexed extension members --- src/Compiler/Checking/NameResolution.fs | 46 +++++++++++++------------ 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/src/Compiler/Checking/NameResolution.fs b/src/Compiler/Checking/NameResolution.fs index 4ed07e463d0..0474d7db8c5 100644 --- a/src/Compiler/Checking/NameResolution.fs +++ b/src/Compiler/Checking/NameResolution.fs @@ -728,7 +728,30 @@ let SelectMethInfosFromExtMembers (infoReader: InfoReader) optFilter apparentTy /// Query the available extension methods of a type (including extension methods for inherited types) let ExtensionMethInfosOfTypeInScope (collectionSettings: ResultCollectionSettings) (infoReader: InfoReader) (nenv: NameResolutionEnv) ad optFilter isInstanceFilter m ty = - let extMemsDangling = SelectMethInfosFromExtMembers infoReader optFilter ty m nenv.eUnindexedExtensionMembers + let g = infoReader.g + let amap = infoReader.amap + + let extMemsDangling = + SelectMethInfosFromExtMembers infoReader optFilter ty m nenv.eUnindexedExtensionMembers + |> List.filter (fun minfo -> + let isAccesible = AccessibilityLogic.IsMethInfoAccessible amap m ad minfo + + let isThisArgEq = + match minfo.GetObjArgTypes(amap, m, []) with + | thisTy :: _ -> + let t1 = stripTyEqnsWrtErasure EraseNone g thisTy + let t2 = stripTyEqnsWrtErasure EraseNone g ty + + match t1, t2 with + | TType_app (tc1, _, _), TType_app (tc2, _, _) -> + tyconRefEq g tc1 tc2 + | _ -> + false + | _ -> + false + + isAccesible && isThisArgEq) + if collectionSettings = ResultCollectionSettings.AtMostOneResult && not (isNil extMemsDangling) then extMemsDangling else @@ -743,27 +766,6 @@ let ExtensionMethInfosOfTypeInScope (collectionSettings: ResultCollectionSetting | _ -> []) extMemsDangling @ extMemsFromHierarchy |> List.filter (fun minfo -> - let g = infoReader.g - let amap = infoReader.amap - - let isAccesible = AccessibilityLogic.IsMethInfoAccessible amap m ad minfo - - let isThisArgEq = - match minfo.GetObjArgTypes(amap, m, []) with - | thisTy :: _ -> - let t1 = stripTyEqnsWrtErasure EraseNone g thisTy - let t2 = stripTyEqnsWrtErasure EraseNone g ty - - match t1, t2 with - | TType_app (tc1, _, _), TType_app (tc2, _, _) -> - tyconRefEq g tc1 tc2 - | _ -> - false - | _ -> - false - - isAccesible && - isThisArgEq && match isInstanceFilter with | LookupIsInstance.Ambivalent -> true | LookupIsInstance.Yes -> minfo.IsInstance From 137dbf5086b75f2c5c56729848398849c5c1654d Mon Sep 17 00:00:00 2001 From: evgTSV Date: Sat, 4 Apr 2026 11:42:36 +0500 Subject: [PATCH 06/12] Turn off types eq check --- src/Compiler/Checking/NameResolution.fs | 36 ++++++++++++------------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/src/Compiler/Checking/NameResolution.fs b/src/Compiler/Checking/NameResolution.fs index 0474d7db8c5..d7ab0d9b102 100644 --- a/src/Compiler/Checking/NameResolution.fs +++ b/src/Compiler/Checking/NameResolution.fs @@ -728,29 +728,24 @@ let SelectMethInfosFromExtMembers (infoReader: InfoReader) optFilter apparentTy /// Query the available extension methods of a type (including extension methods for inherited types) let ExtensionMethInfosOfTypeInScope (collectionSettings: ResultCollectionSettings) (infoReader: InfoReader) (nenv: NameResolutionEnv) ad optFilter isInstanceFilter m ty = - let g = infoReader.g + // let g = infoReader.g let amap = infoReader.amap let extMemsDangling = SelectMethInfosFromExtMembers infoReader optFilter ty m nenv.eUnindexedExtensionMembers - |> List.filter (fun minfo -> - let isAccesible = AccessibilityLogic.IsMethInfoAccessible amap m ad minfo - - let isThisArgEq = - match minfo.GetObjArgTypes(amap, m, []) with - | thisTy :: _ -> - let t1 = stripTyEqnsWrtErasure EraseNone g thisTy - let t2 = stripTyEqnsWrtErasure EraseNone g ty - - match t1, t2 with - | TType_app (tc1, _, _), TType_app (tc2, _, _) -> - tyconRefEq g tc1 tc2 - | _ -> - false - | _ -> - false - - isAccesible && isThisArgEq) + (* |> List.filter (fun minfo -> + match minfo.GetObjArgTypes(amap, m, []) with + | thisTy :: _ -> + let t1 = thisTy |> stripTyEqns g + let t2 = ty |> stripTyEqns g + + match t1, t2 with + | TType_app (tc1, _, _), TType_app (tc2, _, _) -> + tyconRefEq g tc1 tc2 + | _ -> + false + | _ -> + false) *) if collectionSettings = ResultCollectionSettings.AtMostOneResult && not (isNil extMemsDangling) then extMemsDangling @@ -766,6 +761,9 @@ let ExtensionMethInfosOfTypeInScope (collectionSettings: ResultCollectionSetting | _ -> []) extMemsDangling @ extMemsFromHierarchy |> List.filter (fun minfo -> + let isAccesible = AccessibilityLogic.IsMethInfoAccessible amap m ad minfo + + isAccesible && match isInstanceFilter with | LookupIsInstance.Ambivalent -> true | LookupIsInstance.Yes -> minfo.IsInstance From bec8732926d690ff3c29c3761b377433099cedbe Mon Sep 17 00:00:00 2001 From: evgTSV Date: Sat, 4 Apr 2026 12:00:25 +0500 Subject: [PATCH 07/12] Turn on fixed types eq check --- src/Compiler/Checking/NameResolution.fs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Compiler/Checking/NameResolution.fs b/src/Compiler/Checking/NameResolution.fs index d7ab0d9b102..7e82f59a179 100644 --- a/src/Compiler/Checking/NameResolution.fs +++ b/src/Compiler/Checking/NameResolution.fs @@ -728,12 +728,12 @@ let SelectMethInfosFromExtMembers (infoReader: InfoReader) optFilter apparentTy /// Query the available extension methods of a type (including extension methods for inherited types) let ExtensionMethInfosOfTypeInScope (collectionSettings: ResultCollectionSettings) (infoReader: InfoReader) (nenv: NameResolutionEnv) ad optFilter isInstanceFilter m ty = - // let g = infoReader.g + let g = infoReader.g let amap = infoReader.amap let extMemsDangling = SelectMethInfosFromExtMembers infoReader optFilter ty m nenv.eUnindexedExtensionMembers - (* |> List.filter (fun minfo -> + |> List.filter (fun minfo -> match minfo.GetObjArgTypes(amap, m, []) with | thisTy :: _ -> let t1 = thisTy |> stripTyEqns g @@ -745,7 +745,7 @@ let ExtensionMethInfosOfTypeInScope (collectionSettings: ResultCollectionSetting | _ -> false | _ -> - false) *) + true) if collectionSettings = ResultCollectionSettings.AtMostOneResult && not (isNil extMemsDangling) then extMemsDangling From 76e285a999f753a797c24e97ed43ee92993b0e66 Mon Sep 17 00:00:00 2001 From: evgTSV Date: Sat, 4 Apr 2026 13:06:15 +0500 Subject: [PATCH 08/12] Replace hard type eq check with type subsumes check --- src/Compiler/Checking/NameResolution.fs | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/Compiler/Checking/NameResolution.fs b/src/Compiler/Checking/NameResolution.fs index 7e82f59a179..de108d8343b 100644 --- a/src/Compiler/Checking/NameResolution.fs +++ b/src/Compiler/Checking/NameResolution.fs @@ -732,18 +732,14 @@ let ExtensionMethInfosOfTypeInScope (collectionSettings: ResultCollectionSetting let amap = infoReader.amap let extMemsDangling = - SelectMethInfosFromExtMembers infoReader optFilter ty m nenv.eUnindexedExtensionMembers + SelectMethInfosFromExtMembers infoReader optFilter ty m nenv.eUnindexedExtensionMembers |> List.filter (fun minfo -> match minfo.GetObjArgTypes(amap, m, []) with | thisTy :: _ -> - let t1 = thisTy |> stripTyEqns g - let t2 = ty |> stripTyEqns g - - match t1, t2 with - | TType_app (tc1, _, _), TType_app (tc2, _, _) -> - tyconRefEq g tc1 tc2 - | _ -> - false + let ty1 = thisTy |> stripTyEqns g + let ty2 = ty |> stripTyEqns g + + TypeRelations.TypeFeasiblySubsumesType 0 g amap m ty1 TypeRelations.CanCoerce ty2 | _ -> true) From 57a193bef707273cf1983adedb4994a243249cd3 Mon Sep 17 00:00:00 2001 From: evgTSV Date: Sat, 4 Apr 2026 13:22:28 +0500 Subject: [PATCH 09/12] Turn off the strict filter for the FSharp.Core compilation stage --- src/Compiler/Checking/NameResolution.fs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Compiler/Checking/NameResolution.fs b/src/Compiler/Checking/NameResolution.fs index de108d8343b..a65827514ef 100644 --- a/src/Compiler/Checking/NameResolution.fs +++ b/src/Compiler/Checking/NameResolution.fs @@ -734,6 +734,7 @@ let ExtensionMethInfosOfTypeInScope (collectionSettings: ResultCollectionSetting let extMemsDangling = SelectMethInfosFromExtMembers infoReader optFilter ty m nenv.eUnindexedExtensionMembers |> List.filter (fun minfo -> + g.compilingFSharpCore || match minfo.GetObjArgTypes(amap, m, []) with | thisTy :: _ -> let ty1 = thisTy |> stripTyEqns g From 5d0d1b12b30e292f5bcb43b7387732ec6950e3f4 Mon Sep 17 00:00:00 2001 From: evgTSV Date: Sat, 4 Apr 2026 13:52:34 +0500 Subject: [PATCH 10/12] Skip the filter for non CSharpStyle extension members --- src/Compiler/Checking/NameResolution.fs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Compiler/Checking/NameResolution.fs b/src/Compiler/Checking/NameResolution.fs index a65827514ef..e002ed24c9f 100644 --- a/src/Compiler/Checking/NameResolution.fs +++ b/src/Compiler/Checking/NameResolution.fs @@ -734,7 +734,7 @@ let ExtensionMethInfosOfTypeInScope (collectionSettings: ResultCollectionSetting let extMemsDangling = SelectMethInfosFromExtMembers infoReader optFilter ty m nenv.eUnindexedExtensionMembers |> List.filter (fun minfo -> - g.compilingFSharpCore || + not minfo.IsCSharpStyleExtensionMember || match minfo.GetObjArgTypes(amap, m, []) with | thisTy :: _ -> let ty1 = thisTy |> stripTyEqns g From 9c6dc6505ead71e83ac982ab4155007020f615b9 Mon Sep 17 00:00:00 2001 From: evgTSV Date: Sat, 4 Apr 2026 14:07:41 +0500 Subject: [PATCH 11/12] Move the filter to CE pipeline --- .../Expressions/CheckComputationExpressions.fs | 11 +++++++++++ src/Compiler/Checking/NameResolution.fs | 14 +------------- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/src/Compiler/Checking/Expressions/CheckComputationExpressions.fs b/src/Compiler/Checking/Expressions/CheckComputationExpressions.fs index 0899ff2d09f..0291c14268b 100644 --- a/src/Compiler/Checking/Expressions/CheckComputationExpressions.fs +++ b/src/Compiler/Checking/Expressions/CheckComputationExpressions.fs @@ -998,7 +998,18 @@ let inline addVarsToVarSpace (varSpace: LazyWithContext ) let tryFindBuilderMethod (ceenv: ComputationExpressionContext<_>) (m: range) (methodName: string) = + let g = ceenv.cenv.g + let amap = ceenv.cenv.amap TryFindIntrinsicOrExtensionMethInfo ResultCollectionSettings.AtMostOneResult ceenv.cenv ceenv.env m ceenv.ad methodName ceenv.builderTy + |> List.filter (fun minfo -> + match minfo.GetObjArgTypes(amap, m, []) with + | thisTy :: _ -> + let ty1 = thisTy |> stripTyEqns g + let ty2 = ceenv.builderTy |> stripTyEqns g + + TypeRelations.TypeFeasiblySubsumesType 0 g amap m ty1 TypeRelations.CanCoerce ty2 + | _ -> + false) let hasBuilderMethod ceenv m methodName = tryFindBuilderMethod ceenv m methodName |> isNil |> not diff --git a/src/Compiler/Checking/NameResolution.fs b/src/Compiler/Checking/NameResolution.fs index e002ed24c9f..15a8bddcbe9 100644 --- a/src/Compiler/Checking/NameResolution.fs +++ b/src/Compiler/Checking/NameResolution.fs @@ -728,21 +728,9 @@ let SelectMethInfosFromExtMembers (infoReader: InfoReader) optFilter apparentTy /// Query the available extension methods of a type (including extension methods for inherited types) let ExtensionMethInfosOfTypeInScope (collectionSettings: ResultCollectionSettings) (infoReader: InfoReader) (nenv: NameResolutionEnv) ad optFilter isInstanceFilter m ty = - let g = infoReader.g let amap = infoReader.amap - let extMemsDangling = - SelectMethInfosFromExtMembers infoReader optFilter ty m nenv.eUnindexedExtensionMembers - |> List.filter (fun minfo -> - not minfo.IsCSharpStyleExtensionMember || - match minfo.GetObjArgTypes(amap, m, []) with - | thisTy :: _ -> - let ty1 = thisTy |> stripTyEqns g - let ty2 = ty |> stripTyEqns g - - TypeRelations.TypeFeasiblySubsumesType 0 g amap m ty1 TypeRelations.CanCoerce ty2 - | _ -> - true) + let extMemsDangling = SelectMethInfosFromExtMembers infoReader optFilter ty m nenv.eUnindexedExtensionMembers if collectionSettings = ResultCollectionSettings.AtMostOneResult && not (isNil extMemsDangling) then extMemsDangling From 0a74bdbdf5db6a8c938d56c88325a2e7a61e26b5 Mon Sep 17 00:00:00 2001 From: evgTSV Date: Sat, 4 Apr 2026 18:25:20 +0500 Subject: [PATCH 12/12] Create the function to check the 'this' arg in extension methods; Add the new test case --- .../CheckComputationExpressions.fs | 12 +----- .../Checking/Expressions/CheckExpressions.fs | 1 + src/Compiler/Checking/NameResolution.fs | 20 ++++++++++ src/Compiler/Checking/NameResolution.fsi | 9 +++++ .../CEExtensionMethodCapture.fs | 37 ++++++++++++++++++- 5 files changed, 66 insertions(+), 13 deletions(-) diff --git a/src/Compiler/Checking/Expressions/CheckComputationExpressions.fs b/src/Compiler/Checking/Expressions/CheckComputationExpressions.fs index 0291c14268b..fad2b2d6683 100644 --- a/src/Compiler/Checking/Expressions/CheckComputationExpressions.fs +++ b/src/Compiler/Checking/Expressions/CheckComputationExpressions.fs @@ -67,6 +67,7 @@ let inline noTailCall ceenv = { ceenv with tailCall = false } let inline TryFindIntrinsicOrExtensionMethInfo collectionSettings (cenv: cenv) (env: TcEnv) m ad nm ty = AllMethInfosOfTypeInScope collectionSettings cenv.infoReader env.NameEnv (Some nm) ad IgnoreOverrides m ty + |> List.filter (IsExtensionMethCompatibleWithTy cenv.g cenv.amap m ty) /// Ignores an attribute let inline IgnoreAttribute _ = None @@ -998,18 +999,7 @@ let inline addVarsToVarSpace (varSpace: LazyWithContext ) let tryFindBuilderMethod (ceenv: ComputationExpressionContext<_>) (m: range) (methodName: string) = - let g = ceenv.cenv.g - let amap = ceenv.cenv.amap TryFindIntrinsicOrExtensionMethInfo ResultCollectionSettings.AtMostOneResult ceenv.cenv ceenv.env m ceenv.ad methodName ceenv.builderTy - |> List.filter (fun minfo -> - match minfo.GetObjArgTypes(amap, m, []) with - | thisTy :: _ -> - let ty1 = thisTy |> stripTyEqns g - let ty2 = ceenv.builderTy |> stripTyEqns g - - TypeRelations.TypeFeasiblySubsumesType 0 g amap m ty1 TypeRelations.CanCoerce ty2 - | _ -> - false) let hasBuilderMethod ceenv m methodName = tryFindBuilderMethod ceenv m methodName |> isNil |> not diff --git a/src/Compiler/Checking/Expressions/CheckExpressions.fs b/src/Compiler/Checking/Expressions/CheckExpressions.fs index 635a0dff045..6f17d660668 100644 --- a/src/Compiler/Checking/Expressions/CheckExpressions.fs +++ b/src/Compiler/Checking/Expressions/CheckExpressions.fs @@ -3121,6 +3121,7 @@ let BuildPossiblyConditionalMethodCall (cenv: cenv) env isMutable m isProp minfo let TryFindIntrinsicOrExtensionMethInfo collectionSettings (cenv: cenv) (env: TcEnv) m ad nm ty = AllMethInfosOfTypeInScope collectionSettings cenv.infoReader env.NameEnv (Some nm) ad IgnoreOverrides m ty + |> List.filter (IsExtensionMethCompatibleWithTy cenv.g cenv.amap m ty) let TryFindFSharpSignatureInstanceGetterProperty (cenv: cenv) (env: TcEnv) m nm ty (sigTys: TType list) = let g = cenv.g diff --git a/src/Compiler/Checking/NameResolution.fs b/src/Compiler/Checking/NameResolution.fs index 15a8bddcbe9..c01420ad633 100644 --- a/src/Compiler/Checking/NameResolution.fs +++ b/src/Compiler/Checking/NameResolution.fs @@ -762,6 +762,26 @@ let AllMethInfosOfTypeInScope collectionSettings infoReader nenv optFilter ad fi else intrinsic @ ExtensionMethInfosOfTypeInScope collectionSettings infoReader nenv ad optFilter LookupIsInstance.Ambivalent m ty +let IsExtensionMethCompatibleWithTy g amap m (ty: TType) (minfo: MethInfo) = + not minfo.IsExtensionMember || + match minfo.GetObjArgTypes(amap, m, []) with + | thisTy :: _ -> + let ty1 = thisTy |> stripTyEqns g + let ty2 = ty |> stripTyEqns g + + match ty1, ty2 with + | TType_var (tp1, _), _ -> + tp1.Constraints |> List.exists (function + | TyparConstraint.CoercesTo(targetCTy, _) -> + let cTy = targetCTy |> stripTyEqns g + TypeRelations.TypeFeasiblySubsumesType 0 g amap m cTy TypeRelations.CanCoerce ty2 + | _ -> false) + | _, TType_var _ -> true + | _ -> + TypeRelations.TypeFeasiblySubsumesType 0 g amap m ty1 TypeRelations.CanCoerce ty2 + | _ -> + true + //------------------------------------------------------------------------- // Helpers to do with building environments //------------------------------------------------------------------------- diff --git a/src/Compiler/Checking/NameResolution.fsi b/src/Compiler/Checking/NameResolution.fsi index b5f6a7172aa..ecadc7430de 100755 --- a/src/Compiler/Checking/NameResolution.fsi +++ b/src/Compiler/Checking/NameResolution.fsi @@ -689,6 +689,15 @@ val internal AllMethInfosOfTypeInScope: ty: TType -> MethInfo list +/// Check whether the 'this' argument of an extension method is compatible with the target type +val internal IsExtensionMethCompatibleWithTy: + g: TcGlobals -> + amap: ImportMap -> + m: range -> + ty: TType -> + minfo: MethInfo -> + bool + /// Used to report an error condition where name resolution failed due to an indeterminate type exception internal IndeterminateType of range diff --git a/tests/FSharp.Compiler.ComponentTests/Conformance/Expressions/ComputationExpressions/CEExtensionMethodCapture.fs b/tests/FSharp.Compiler.ComponentTests/Conformance/Expressions/ComputationExpressions/CEExtensionMethodCapture.fs index 2b42463330e..027b93933b7 100644 --- a/tests/FSharp.Compiler.ComponentTests/Conformance/Expressions/ComputationExpressions/CEExtensionMethodCapture.fs +++ b/tests/FSharp.Compiler.ComponentTests/Conformance/Expressions/ComputationExpressions/CEExtensionMethodCapture.fs @@ -53,7 +53,7 @@ let ``CE doesn't capture an extension method with generic type``() = AsyncSeq(x) [] - type PrivateExtensions = + type PublicExtensions = [] static member inline Run(this: #FooClass) = this @@ -100,4 +100,37 @@ let ``CE captures a public extension method and procudes an error due to invalid """ |> asExe |> compile - |> shouldFail \ No newline at end of file + |> shouldFail + +// Deliberately trigger an error to ensure that a method is captured +[] +let ``CE captures a public extension method with valid generic constrainted type and procudes an error due to invalid args``() = + FSharp """ + open System.Runtime.CompilerServices + + type AsyncSeq<'T>(i: 'T) = + class + let l = [i] + member this.Data = l + end + + type AsyncSeqBuilder() = + member _.Yield(x: 'T) : AsyncSeq<'T> = + AsyncSeq(x) + + [] + type PublicExtensions = + [] + static member inline Run(this: #AsyncSeqBuilder, invalidArg: string) = + this + + let asyncSeq = AsyncSeqBuilder() + + let xs : AsyncSeq = + asyncSeq { + yield 1 + } + """ + |> asExe + |> compile + |> shouldFail