From 967b80cb8127a55f3b7aad9067428b63007cbf75 Mon Sep 17 00:00:00 2001 From: evgTSV Date: Thu, 7 May 2026 00:13:37 +0500 Subject: [PATCH 1/7] Fix #19664: Extension static methods are resolved if similar intrisic methods exist --- src/Compiler/Checking/NameResolution.fs | 8 ++--- .../StaticMethodResolution.fs | 32 +++++++++++++++++++ .../FSharp.Compiler.ComponentTests.fsproj | 1 + 3 files changed, 35 insertions(+), 6 deletions(-) create mode 100644 tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/MethodResolution/StaticMethodResolution.fs diff --git a/src/Compiler/Checking/NameResolution.fs b/src/Compiler/Checking/NameResolution.fs index de5dcff43ad..7363309ec92 100644 --- a/src/Compiler/Checking/NameResolution.fs +++ b/src/Compiler/Checking/NameResolution.fs @@ -4165,7 +4165,7 @@ let ResolveNestedField sink (ncenv: NameResolver) nenv ad recdTy lid = // // QUERY (instantiationGenerator cleanup): it would be really nice not to flow instantiationGenerator to here. let private ResolveExprDotLongIdent (ncenv: NameResolver) m ad nenv ty (id: Ident) rest (typeNameResInfo: TypeNameResolutionInfo) findFlag maybeArgExpr = - let lookupKind = LookupKind.Expr LookupIsInstance.Yes + let lookupKind = LookupKind.Expr LookupIsInstance.Ambivalent let adhocDotSearchAccessible = AtMostOneResult m (ResolveLongIdentInTypePrim ncenv nenv lookupKind ResolutionInfo.Empty 1 m ad id rest findFlag typeNameResInfo ty maybeArgExpr) match adhocDotSearchAccessible with | Exception _ -> @@ -4184,11 +4184,7 @@ let private ResolveExprDotLongIdent (ncenv: NameResolver) m ad nenv ty (id: Iden OneSuccess (ResolutionInfo.Empty, item, rest) | _ -> NoResultsOrUsefulErrors - let adhocDotSearchAll () = - let lookupKind = LookupKind.Expr LookupIsInstance.Ambivalent - ResolveLongIdentInTypePrim ncenv nenv lookupKind ResolutionInfo.Empty 1 m AccessibleFromSomeFSharpCode id rest findFlag typeNameResInfo ty None - - dotFieldIdSearch +++ adhocDotSearchAll + dotFieldIdSearch |> AtMostOneResult m |> ForceRaise | _ -> diff --git a/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/MethodResolution/StaticMethodResolution.fs b/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/MethodResolution/StaticMethodResolution.fs new file mode 100644 index 00000000000..4d0b0b41390 --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/MethodResolution/StaticMethodResolution.fs @@ -0,0 +1,32 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. + +namespace Conformance.BasicGrammarElements + +open FSharp.Test.Compiler +open Xunit + +module StaticMethodResolution = + + // So that the compiler doesn't treat an extension method as intrisic + // we place a method to another module. + [] + let ``Extension static method is resolved correctly when one or many intrisic candidates are found``() = + Fsx """ +module Extensions = + + type StaticGeneric<'T>() = + static member Bar() = () + + [] + module StaticGenericExtensions = + type StaticGeneric<'T> with + static member Bar(_: int) = () + +module Program = + open Extensions + + StaticGeneric.Bar(42) // StaticGeneric is just an ident + StaticGeneric.Bar(42) // StaticGeneric is an expression + """ + |> typecheck + |> shouldSucceed \ 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 aa5a5cc11f2..5e7285236d6 100644 --- a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj +++ b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj @@ -64,6 +64,7 @@ + From 24e7b57a13c8d933f419ed71539b53e7c7e74397 Mon Sep 17 00:00:00 2001 From: evgTSV Date: Thu, 7 May 2026 00:37:48 +0500 Subject: [PATCH 2/7] Fix typos --- .../MethodResolution/StaticMethodResolution.fs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/MethodResolution/StaticMethodResolution.fs b/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/MethodResolution/StaticMethodResolution.fs index 4d0b0b41390..2627400d2d4 100644 --- a/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/MethodResolution/StaticMethodResolution.fs +++ b/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/MethodResolution/StaticMethodResolution.fs @@ -7,10 +7,10 @@ open Xunit module StaticMethodResolution = - // So that the compiler doesn't treat an extension method as intrisic + // So that the compiler doesn't treat an extension method as intrinsic // we place a method to another module. [] - let ``Extension static method is resolved correctly when one or many intrisic candidates are found``() = + let ``Extension static method is resolved correctly when one or many intrinsic candidates are found``() = Fsx """ module Extensions = From 2e1d15671cf6e5ab044d5f5d257f7039fb09a759 Mon Sep 17 00:00:00 2001 From: evgTSV Date: Thu, 7 May 2026 00:46:45 +0500 Subject: [PATCH 3/7] Add a record to the release notes --- 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 c4750e59e1f..a58b2c0b598 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md +++ b/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md @@ -47,6 +47,7 @@ * Fix signature generation: SRTP constraints use postfix syntax that fails conformance, now uses explicit type param declarations. ([Issue #19594](https://github.com/dotnet/fsharp/issues/19594), [PR #19609](https://github.com/dotnet/fsharp/pull/19609)) * Fix signature generation: type params with special characters missing backtick escaping. ([Issue #19595](https://github.com/dotnet/fsharp/issues/19595), [PR #19609](https://github.com/dotnet/fsharp/pull/19609)) * Fix internal error when using custom attribute with `[]` value type parameter and no `[]`. ([Issue #8353](https://github.com/dotnet/fsharp/issues/8353), [PR #19484](https://github.com/dotnet/fsharp/pull/19484)) +* Fix overload resolution of static member extension if one or more intrinsics candidates exist ([Issue #19664](https://github.com/dotnet/fsharp/issues/19664), [PR #19698](https://github.com/dotnet/fsharp/pull/19698)) ### Added From 8095590f3928897ab421b244473d1735f1d16e95 Mon Sep 17 00:00:00 2001 From: evgTSV Date: Sun, 10 May 2026 16:17:01 +0500 Subject: [PATCH 4/7] Make isInstanceFilter ambivalent if either static meth when Yes or instance meth when No is found --- src/Compiler/Checking/NameResolution.fs | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/src/Compiler/Checking/NameResolution.fs b/src/Compiler/Checking/NameResolution.fs index 7363309ec92..9357299f0bd 100644 --- a/src/Compiler/Checking/NameResolution.fs +++ b/src/Compiler/Checking/NameResolution.fs @@ -2874,6 +2874,20 @@ let rec ResolveLongIdentInTypePrim (ncenv: NameResolver) nenv lookupKind (resInf | Some(MethodItem msets) when isLookUpExpr -> let minfos = msets |> ExcludeHiddenOfMethInfos g ncenv.amap m + let isAmbivalent = + minfos + |> List.exists (fun minfo -> + match isInstanceFilter with + | LookupIsInstance.Yes -> not minfo.IsInstance + | LookupIsInstance.No -> minfo.IsInstance + | LookupIsInstance.Ambivalent -> true) + + let isInstanceFilter = + if isAmbivalent then + LookupIsInstance.Ambivalent + else + isInstanceFilter + // fold the available extension members into the overload resolution let extensionMethInfos = ExtensionMethInfosOfTypeInScope ResultCollectionSettings.AllResults ncenv.InfoReader nenv ad optFilter isInstanceFilter m ty @@ -4165,7 +4179,7 @@ let ResolveNestedField sink (ncenv: NameResolver) nenv ad recdTy lid = // // QUERY (instantiationGenerator cleanup): it would be really nice not to flow instantiationGenerator to here. let private ResolveExprDotLongIdent (ncenv: NameResolver) m ad nenv ty (id: Ident) rest (typeNameResInfo: TypeNameResolutionInfo) findFlag maybeArgExpr = - let lookupKind = LookupKind.Expr LookupIsInstance.Ambivalent + let lookupKind = LookupKind.Expr LookupIsInstance.Yes let adhocDotSearchAccessible = AtMostOneResult m (ResolveLongIdentInTypePrim ncenv nenv lookupKind ResolutionInfo.Empty 1 m ad id rest findFlag typeNameResInfo ty maybeArgExpr) match adhocDotSearchAccessible with | Exception _ -> @@ -4184,7 +4198,11 @@ let private ResolveExprDotLongIdent (ncenv: NameResolver) m ad nenv ty (id: Iden OneSuccess (ResolutionInfo.Empty, item, rest) | _ -> NoResultsOrUsefulErrors - dotFieldIdSearch + let adhocDotSearchAll () = + let lookupKind = LookupKind.Expr LookupIsInstance.Ambivalent + ResolveLongIdentInTypePrim ncenv nenv lookupKind ResolutionInfo.Empty 1 m AccessibleFromSomeFSharpCode id rest findFlag typeNameResInfo ty None + + dotFieldIdSearch +++ adhocDotSearchAll |> AtMostOneResult m |> ForceRaise | _ -> From 403cb8275ff789da347107672a38a54ddf93193a Mon Sep 17 00:00:00 2001 From: evgTSV Date: Sun, 10 May 2026 16:55:15 +0500 Subject: [PATCH 5/7] Fix tests --- .../MethodResolution/StaticMethodResolution.fs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/MethodResolution/StaticMethodResolution.fs b/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/MethodResolution/StaticMethodResolution.fs index 2627400d2d4..6f2f48f209a 100644 --- a/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/MethodResolution/StaticMethodResolution.fs +++ b/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/MethodResolution/StaticMethodResolution.fs @@ -28,5 +28,6 @@ module Program = StaticGeneric.Bar(42) // StaticGeneric is just an ident StaticGeneric.Bar(42) // StaticGeneric is an expression """ + |> withOptions ["--nowarn:1125"] |> typecheck |> shouldSucceed \ No newline at end of file From b29061dfae0ecd7d993d33d9896a9028b79ab133 Mon Sep 17 00:00:00 2001 From: evgTSV Date: Mon, 25 May 2026 00:27:40 +0500 Subject: [PATCH 6/7] Pass isStatic to the ResolveExprDotLongIdent func and use it in order to determine a lookupKind --- src/Compiler/Checking/NameResolution.fs | 22 +++++-------------- .../FSharp.Compiler.ComponentTests.fsproj | 1 + 2 files changed, 6 insertions(+), 17 deletions(-) diff --git a/src/Compiler/Checking/NameResolution.fs b/src/Compiler/Checking/NameResolution.fs index 9357299f0bd..c380189f82a 100644 --- a/src/Compiler/Checking/NameResolution.fs +++ b/src/Compiler/Checking/NameResolution.fs @@ -2874,20 +2874,6 @@ let rec ResolveLongIdentInTypePrim (ncenv: NameResolver) nenv lookupKind (resInf | Some(MethodItem msets) when isLookUpExpr -> let minfos = msets |> ExcludeHiddenOfMethInfos g ncenv.amap m - let isAmbivalent = - minfos - |> List.exists (fun minfo -> - match isInstanceFilter with - | LookupIsInstance.Yes -> not minfo.IsInstance - | LookupIsInstance.No -> minfo.IsInstance - | LookupIsInstance.Ambivalent -> true) - - let isInstanceFilter = - if isAmbivalent then - LookupIsInstance.Ambivalent - else - isInstanceFilter - // fold the available extension members into the overload resolution let extensionMethInfos = ExtensionMethInfosOfTypeInScope ResultCollectionSettings.AllResults ncenv.InfoReader nenv ad optFilter isInstanceFilter m ty @@ -4178,8 +4164,10 @@ let ResolveNestedField sink (ncenv: NameResolver) nenv ad recdTy lid = /// determine any valid members // // QUERY (instantiationGenerator cleanup): it would be really nice not to flow instantiationGenerator to here. -let private ResolveExprDotLongIdent (ncenv: NameResolver) m ad nenv ty (id: Ident) rest (typeNameResInfo: TypeNameResolutionInfo) findFlag maybeArgExpr = - let lookupKind = LookupKind.Expr LookupIsInstance.Yes +let private ResolveExprDotLongIdent (ncenv: NameResolver) m ad nenv ty (id: Ident) rest (typeNameResInfo: TypeNameResolutionInfo) findFlag staticOnly maybeArgExpr = + let lookupKind = + if staticOnly then LookupKind.Expr LookupIsInstance.No + else LookupKind.Expr LookupIsInstance.Yes let adhocDotSearchAccessible = AtMostOneResult m (ResolveLongIdentInTypePrim ncenv nenv lookupKind ResolutionInfo.Empty 1 m ad id rest findFlag typeNameResInfo ty maybeArgExpr) match adhocDotSearchAccessible with | Exception _ -> @@ -4338,7 +4326,7 @@ let ResolveExprDotLongIdentAndComputeRange (sink: TcResultsSink) (ncenv: NameRes let resInfo, item, rest = match lid with | id :: rest -> - ResolveExprDotLongIdent ncenv wholem ad nenv ty id rest typeNameResInfo findFlag maybeAppliedArgExpr + ResolveExprDotLongIdent ncenv wholem ad nenv ty id rest typeNameResInfo findFlag staticOnly maybeAppliedArgExpr | _ -> error(InternalError("ResolveExprDotLongIdentAndComputeRange", wholem)) let itemRange = ComputeItemRange wholem lid rest resInfo, item, rest, itemRange diff --git a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj index 5e7285236d6..9dcbc9c08b3 100644 --- a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj +++ b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj @@ -65,6 +65,7 @@ + From 30231c5646491960f4d5c7e349d720aa245634da Mon Sep 17 00:00:00 2001 From: evgTSV Date: Mon, 25 May 2026 00:28:53 +0500 Subject: [PATCH 7/7] [#19797, #19664] Add regression tests --- .../StaticMethodResolution.fs | 24 ++++++---- .../StaticPropertyResolution.fs | 45 +++++++++++++++++++ 2 files changed, 61 insertions(+), 8 deletions(-) create mode 100644 tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/PropertyResolution/StaticPropertyResolution.fs diff --git a/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/MethodResolution/StaticMethodResolution.fs b/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/MethodResolution/StaticMethodResolution.fs index 6f2f48f209a..c6602a0f1be 100644 --- a/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/MethodResolution/StaticMethodResolution.fs +++ b/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/MethodResolution/StaticMethodResolution.fs @@ -7,15 +7,23 @@ open Xunit module StaticMethodResolution = - // So that the compiler doesn't treat an extension method as intrinsic - // we place a method to another module. + // Regression test for https://github.com/dotnet/fsharp/issues/19664 + // + // When a static extension method is defined in a *different* [] module than + // the generic type it extends, and shares its name with an intrinsic static member, + // resolving the call via the explicit-type-argument syntax `Type.Member(...)` + // previously failed with FS0505. The non-generic dotted form `Type.Member(...)` + // resolved correctly, so any regression test that omits the explicit type arguments + // does not actually exercise the bug. See the discussion at + // https://github.com/dotnet/fsharp/issues/19675#issuecomment-4373059900. [] - let ``Extension static method is resolved correctly when one or many intrinsic candidates are found``() = + let ``Static extension on generic type resolves with explicit type arguments``() = Fsx """ module Extensions = - type StaticGeneric<'T>() = + type StaticGeneric<'T> = static member Bar() = () + static member Bar(_: int, _: int) = () [] module StaticGenericExtensions = @@ -24,10 +32,10 @@ module Extensions = module Program = open Extensions - - StaticGeneric.Bar(42) // StaticGeneric is just an ident - StaticGeneric.Bar(42) // StaticGeneric is an expression + + StaticGeneric.Bar() // intrinsic, 0 args + StaticGeneric.Bar(42) // regressed: extension, 1 arg, see issue 19664 (FS0505) + StaticGeneric.Bar(42, 0) // intrinsic, 2 args """ - |> withOptions ["--nowarn:1125"] |> typecheck |> shouldSucceed \ No newline at end of file diff --git a/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/PropertyResolution/StaticPropertyResolution.fs b/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/PropertyResolution/StaticPropertyResolution.fs new file mode 100644 index 00000000000..33fe8740def --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/PropertyResolution/StaticPropertyResolution.fs @@ -0,0 +1,45 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. + +namespace Conformance.BasicGrammarElements + +open FSharp.Test.Compiler +open Xunit + +module StaticPropertyResolution = + + // Regression test for static property accessors (getter/setter) resolution. + // Related to https://github.com/dotnet/fsharp/issues/19797 + // + // When a static extension 'set' or 'get' accessor is defined in a *different* module + // than the generic type it extends, and the corresponding property has the other intrinsic + // static accessor + // (i.e. the intrinsic property has a 'get' and the extension has a 'set', or vice versa) + // + // For instance, resolving the assignment via explicit-type-argument syntax, + // where the 'set' accessor is an extension and the 'get' accessor is intrinsic, + // Type.Property <- value previously failed with FS0810. + // + // The non-generic dotted form `Type.Member` + // resolved correctly, so any regression test that omits the explicit type arguments + // does not actually exercise the bug. See the discussion at + // https://github.com/dotnet/fsharp/issues/19675#issuecomment-4373059900. + [] + let ``Static property on generic type resolves the extension accessor when the other is intrinsic``() = + Fsx """ +module Lib = + + type Label<'T> = + static member Text with get() = "Static Intrinsic" + + [] + module Utils = + type Label<'T> with + static member Text with set (v) = printfn "Extension" + +module Program = + open Lib + + Label.Text <- "Text" // regressed: extension setter, see issue 19797 (FS0810) + """ + |> typecheck + |> shouldSucceed \ No newline at end of file