From 17139b0e8fd7971b578b0a43692dfc223e71fd3b Mon Sep 17 00:00:00 2001 From: Copilot Date: Thu, 11 Jun 2026 08:07:48 +0200 Subject: [PATCH 1/3] Add RED tests for QuickParse `]`-tail dot handling (#4966) Tests fail on current code: 'a.[0].Data.' wrongly returns QualifyingIdents=[\"Data\"]. Sprint 02 will apply the one-char fix in QuickParse.fs. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../QuickParseTests.fs | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/tests/FSharp.Compiler.Service.Tests/QuickParseTests.fs b/tests/FSharp.Compiler.Service.Tests/QuickParseTests.fs index 905e043d735..6f4dad7a97d 100644 --- a/tests/FSharp.Compiler.Service.Tests/QuickParseTests.fs +++ b/tests/FSharp.Compiler.Service.Tests/QuickParseTests.fs @@ -71,3 +71,60 @@ let ``QuickParse does not treat question mark as identifier in other contexts``( | None -> // Or we might get None, which is also acceptable () + +// --- Issue #4966 regression tests ----------------------------------------- +// QuickParse.GetPartialLongNameEx had a special case for ')' before a trigger +// dot (so 'f(x).Name.' did not feed 'Name' back as a qualifying ident). +// The equivalent case for ']' was missing, so 'a.[0].Data.' wrongly returned +// QualifyingIdents = ["Data"] and the completion engine then resolved 'Data' +// as a module/type prefix. After the fix, ']' must behave like ')': the +// returned PartialLongName must be empty so the completer falls back to +// expression-typings. + +[] +[] // legacy explicit-dot indexer +[] // modern indexer syntax +[] // list literal indexer/tail +[] // nested explicit-dot indexer +[] // nested modern indexer +let ``GetPartialLongNameEx returns empty for dot after closing bracket`` (lineStr: string) = + // cursor is right after the trailing '.', so index is the position of that dot + let index = lineStr.Length - 1 + Assert.Equal('.', lineStr[index]) + + let pln = QuickParse.GetPartialLongNameEx(lineStr, index) + + Assert.Equal([], pln.QualifyingIdents) + Assert.Equal("", pln.PartialIdent) + +// Regression guard: the pre-existing ')' special case must keep working +// exactly as it does on `main`. These cases pass today; they must keep passing +// after the Sprint-02 fix. +[] +[] +[] +[] +let ``GetPartialLongNameEx returns empty for dot after closing paren`` (lineStr: string) = + let index = lineStr.Length - 1 + Assert.Equal('.', lineStr[index]) + + let pln = QuickParse.GetPartialLongNameEx(lineStr, index) + + Assert.Equal([], pln.QualifyingIdents) + Assert.Equal("", pln.PartialIdent) + +// Negative cases: simple long-ident with no bracket/paren tail must STILL be +// returned as a qualifying ident. The fix must not over-throw-away. +[] +[] +[] +[] +let ``GetPartialLongNameEx preserves plain long identifiers`` (lineStr: string, lastQualifier: string) = + let index = lineStr.Length - 1 + Assert.Equal('.', lineStr[index]) + + let pln = QuickParse.GetPartialLongNameEx(lineStr, index) + + Assert.NotEmpty pln.QualifyingIdents + Assert.Equal(lastQualifier, List.last pln.QualifyingIdents) + Assert.Equal("", pln.PartialIdent) From a9b4ee6e6fae0a572675fd2fce27275116e799de Mon Sep 17 00:00:00 2001 From: Copilot Date: Thu, 11 Jun 2026 08:23:15 +0200 Subject: [PATCH 2/3] Fix QuickParse: treat `]` before dot like `)` (#4966) Completion on 'a.[0].Data.', 'a[0].Data.', '[1;2].Length.' previously fed 'Data'/'Length' back as a qualifying ident, producing unrelated global completions. Extending the existing ')' special case at QuickParse.fs:410 to also include ']' makes the completer fall back to expression-typings, matching the behaviour for 'f(x).Name.'. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Compiler/Service/QuickParse.fs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Compiler/Service/QuickParse.fs b/src/Compiler/Service/QuickParse.fs index 41345d0c415..8c4dd26619f 100644 --- a/src/Compiler/Service/QuickParse.fs +++ b/src/Compiler/Service/QuickParse.fs @@ -407,7 +407,7 @@ module QuickParse = AtStartOfIdentifier(pos + 1, "" :: current, throwAwayNext, Some pos) elif not (pos > 0 && (IsIdentifierPartCharacter(pos - 1) || IsWhitespace(pos - 1))) then // it's not dots as part.of.a.long.ident, it's e.g. the range operator (..), or some other multi-char operator ending in dot - if lineStr[pos - 1] = ')' then + if lineStr[pos - 1] = ')' || lineStr[pos - 1] = ']' then // one very problematic case is someCall(args).Name // without special logic, we will decide that ). is an operator and parse Name as the plid // but in fact this is an expression tail, and we don't want a plid, rather we need to use expression typings at that location From dd7ecacce6c023a9fe621e9d3df18ab80623a3d7 Mon Sep 17 00:00:00 2001 From: Copilot Date: Thu, 11 Jun 2026 12:06:19 +0200 Subject: [PATCH 3/3] Add release notes for #4966 QuickParse fix Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- 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 965957dbc0c..a76580a8078 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md +++ b/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md @@ -75,6 +75,7 @@ ([PR #19724](https://github.com/dotnet/fsharp/pull/19724)) * Emit debug points at a stack-empty position ([PR #19877](https://github.com/dotnet/fsharp/pull/19877)) * Fix spurious XmlDoc warnings (unknown parameter / no documentation for parameter) under `--warnon:3390` when a get/set property documents the full parameter set across both accessors. ([Issue #13684](https://github.com/dotnet/fsharp/issues/13684), [PR #19884](https://github.com/dotnet/fsharp/pull/19884)) +* Fix dot-completion after indexed expressions (`a.[0].Data.`, `a[0].Data.`, `[1;2].Length.`) returning unrelated global completions instead of expression-typings members. ([Issue #4966](https://github.com/dotnet/fsharp/issues/4966), [PR #19934](https://github.com/dotnet/fsharp/pull/19934)) ### Added