From d0f9a4424f445aafd87935f14d16f6b9b9126296 Mon Sep 17 00:00:00 2001 From: Andrii Chebukin Date: Sun, 20 Sep 2026 21:49:15 +0200 Subject: [PATCH 1/2] Report a hand-written `__` self identifier to the name resolution sink `MakeAndPublishVal` skipped the sink for any `MemberThisVal` literally named `__`. The target was the self identifier that the auto-property desugaring synthesizes, which sits on the *property name's* range and would therefore shadow the property's own symbol -- a range collision, not a name problem. Filtering by name also hid every `member __.M()` a user writes. Declarations go through `MakeAndPublishVal` and were suppressed, while uses in the body resolve normally and were not, so FCS reported the uses of `__` without its declaration. Editor rename then rewrote the body and left `member __.` behind, silently producing code that no longer compiles. Mark the synthesized ident's range synthetic instead, as the adjacent backing field already does. `TcResultsSinkImpl.allowedRange` drops synthetic ranges for every sink notification, so the auto-property self identifier stays invisible and the name check becomes dead code. Co-Authored-By: Claude Opus 5 (1M context) --- src/Compiler/Checking/CheckDeclarations.fs | 2 +- .../Checking/Expressions/CheckExpressions.fs | 10 ++--- .../FSharp.Compiler.Service.Tests/Symbols.fs | 44 +++++++++++++++++++ 3 files changed, 48 insertions(+), 8 deletions(-) diff --git a/src/Compiler/Checking/CheckDeclarations.fs b/src/Compiler/Checking/CheckDeclarations.fs index 76906d027b9..1fe016c8190 100644 --- a/src/Compiler/Checking/CheckDeclarations.fs +++ b/src/Compiler/Checking/CheckDeclarations.fs @@ -4841,7 +4841,7 @@ module TcDeclarations = // Only the keep the non-field-targeted attributes let attribs = attribs |> List.filter (fun a -> match a.Target with Some t when t.idText = "field" -> false | _ -> true) let fldId = ident (CompilerGeneratedName id.idText, mMemberPortion.MakeSynthetic()) - let headPatIds = if isStatic then [id] else [ident ("__", mMemberPortion);id] + let headPatIds = if isStatic then [id] else [ident ("__", mMemberPortion.MakeSynthetic());id] let headPat = SynPat.LongIdent (SynLongIdent(headPatIds, [], List.replicate headPatIds.Length None), None, Some noInferredTypars, SynArgPats.Pats [], None, mMemberPortion) let memberFlags = { memberFlags with GetterOrSetterIsCompilerGenerated = true } let memberFlagsForSet = { memberFlagsForSet with GetterOrSetterIsCompilerGenerated = true } diff --git a/src/Compiler/Checking/Expressions/CheckExpressions.fs b/src/Compiler/Checking/Expressions/CheckExpressions.fs index 3fd7e014332..f90bb60207b 100644 --- a/src/Compiler/Checking/Expressions/CheckExpressions.fs +++ b/src/Compiler/Checking/Expressions/CheckExpressions.fs @@ -1451,13 +1451,9 @@ let MakeAndPublishVal (cenv: cenv) env (altActualParent, inSig, declKind, valRec let shouldNotifySink (vspec: Val) = match vspec.MemberInfo with - // `this` reference named `__`. It's either: - // * generated by compiler for auto properties or - // * provided by source code (i.e. `member _.Method = ...`) - // We don't notify sink about it to prevent generating `FSharpSymbol` for it and appearing in completion list. - | None when - vspec.IsBaseVal || - vspec.IsMemberThisVal && vspec.LogicalName = "__" -> false + // visualfsharp#3699: the range of `base` spans the whole `inherit` expression, so reporting it + // makes its environment the most deeply nested one at the constructor arguments, shadowing theirs. + | None when vspec.IsBaseVal -> false | _ -> true match cenv.tcSink.CurrentSink with diff --git a/tests/FSharp.Compiler.Service.Tests/Symbols.fs b/tests/FSharp.Compiler.Service.Tests/Symbols.fs index 11c7a091226..5d416891f0a 100644 --- a/tests/FSharp.Compiler.Service.Tests/Symbols.fs +++ b/tests/FSharp.Compiler.Service.Tests/Symbols.fs @@ -689,6 +689,50 @@ type Foo = let backingFieldSymbols = allMfvs |> List.filter (fun mfv -> mfv.DisplayName.Contains("@")) Assert.True(backingFieldSymbols.IsEmpty, $"Compiler-generated backing field should not appear, but found: {backingFieldSymbols |> List.map (fun m -> m.DisplayName)}") + let private selfIdentifierRanges (checkResults: FSharpCheckFileResults) = + checkResults.GetAllUsesOfAllSymbolsInFile() + |> Seq.filter (fun su -> + match su.Symbol with + | :? FSharpMemberOrFunctionOrValue as mfv -> mfv.IsMemberThisValue + | _ -> false) + |> Seq.map _.Range + |> Seq.sortBy (fun m -> m.StartLine, m.StartColumn) + |> Seq.toList + + [] + [] + [] + [] + let ``Self identifier is reported at its declaration and at its uses`` (selfId: string) = + let _, checkResults = getParseAndCheckResults $""" +namespace Foo + +type Foo() = + member {selfId}.M() = {selfId}.N() + member {selfId}.N() = 1 +""" + let n = selfId.Length + + match selfIdentifierRanges checkResults with + | [ mDeclInM; mUseInM; mDeclInN ] -> + assertRange (5, 11) (5, 11 + n) mDeclInM + assertRange (5, 18 + n) (5, 18 + 2 * n) mUseInM + assertRange (6, 11) (6, 11 + n) mDeclInN + | ranges -> failwith $"Expected three self identifier symbol uses, got %A{ranges}" + + [] + let ``AutoProperty does not expose its compiler-generated self identifier`` () = + let _, checkResults = getParseAndCheckResults """ +namespace Foo + +type Foo = + member val AutoPropGetSet = 0 with get, set +""" + // The synthesized `__` sits on the property name's range, so reporting it would shadow the property + match selfIdentifierRanges checkResults with + | [] -> () + | ranges -> failwith $"Expected no self identifier symbol uses, got %A{ranges}" + [] let ``Property symbol is resolved for property`` () = let symbols = Checker.getSymbolUses """ From 0ee7a0786958e9d2de07297fdf5aae3d17c1d5c7 Mon Sep 17 00:00:00 2001 From: Andrii Chebukin Date: Sun, 20 Sep 2026 22:15:11 +0200 Subject: [PATCH 2/2] Add release note Co-Authored-By: Claude Opus 5 (1M context) --- 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 d8c0a819a27..2d82952dcfb 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md +++ b/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md @@ -95,6 +95,7 @@ * Fix nativeptr in interfaces leads to TypeLoadException. (Issue [#14508](https://github.com/dotnet/fsharp/issues/14508), [PR #19338](https://github.com/dotnet/fsharp/pull/19338)) * Fix box instruction for literal upcasts. (Issue [#18319](https://github.com/dotnet/fsharp/issues/18319), [PR #19338](https://github.com/dotnet/fsharp/pull/19338)) * Fix Decimal Literal causes InvalidProgramException in Debug builds. (Issue [#18956](https://github.com/dotnet/fsharp/issues/18956), [PR #19338](https://github.com/dotnet/fsharp/pull/19338)) +* Report a self identifier named `__` (as in `member __.M()`) to editor tooling. Only its uses were reported, not its declaration, so Rename rewrote the body of such a member and left `member __.` behind. The self identifier synthesized by the auto-property desugaring, which the name check was really aimed at, is now suppressed by its synthetic range instead. ([Issue #20566](https://github.com/dotnet/fsharp/issues/20566), [PR #20598](https://github.com/dotnet/fsharp/pull/20598)) * Fix `AttributeUsage.AllowMultiple` not being inherited for attributes subclassed in C#. ([Issue #17107](https://github.com/dotnet/fsharp/issues/17107), [PR #19315](https://github.com/dotnet/fsharp/pull/19315)) * Fix signature generation: recursive module `do` binding leaking compiler-generated val. ([Issue #13832](https://github.com/dotnet/fsharp/issues/13832), [PR #19586](https://github.com/dotnet/fsharp/pull/19586)) * Fix signature generation: literal values in attribute arguments now preserve literal identifier name. ([Issue #13810](https://github.com/dotnet/fsharp/issues/13810), [PR #19586](https://github.com/dotnet/fsharp/pull/19586))