Offer every open that resolves the name, and open type where a plain open cannot reach - #20501
Open
xperiandri wants to merge 6 commits into
Open
Offer every open that resolves the name, and open type where a plain open cannot reach#20501xperiandri wants to merge 6 commits into
xperiandri wants to merge 6 commits into
Conversation
The "open namespace" code fix and the completion that adds an open both decided where the declaration goes by recognizing a module header in the text of a line — `line.StartsWith "module" && not (line.EndsWith "=")` — followed by arithmetic that assumed a blank line under that header. Every shape the spelling missed put the open where it does not compile: `[<AutoOpen>] module Ns.Name` written on one line read as an implicit module and took the open above the module declaration, and a header with no blank line under it took the open below the first declaration, leaving the identifier as unresolved as before. InsertionContext.Pos now means one thing for every ScopeKind — the first line inside the scope — taken from the leading keyword the trivia records, the name it introduces and, for a nested module, the `=`. Attributes and doc comments sit above that line by construction, so no spelling has to be recognized. AdjustInsertionPoint keeps only the cosmetic step of stepping over the blank line under a header, and the editor's two insertion paths now share one implementation, which also follows the file's own line endings instead of the host's. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Contributor
❗ Release notes requiredYou can open this PR in browser to add release notes: open in github.dev
Warning No PR link found in some release notes, please consider adding it.
|
Contributor
Author
Contributor
|
🔍 Tooling Safety Check — Affects-Design-Time
|
The formatting-check CI job flagged AddOpenCodeFixProvider.fs. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
xperiandri
force-pushed
the
fix/add-open-type
branch
from
September 9, 2026 23:50
52437eb to
93b84ca
Compare
T-Gro
reviewed
Sep 10, 2026
T-Gro
left a comment
Member
There was a problem hiding this comment.
🤖 🕵️ AI review — verify independently.
| |> Seq.toList | ||
| |> getSuggestionsAsCodeFixes context sourceText | ||
| |> Seq.tryHead)) | ||
| |> getSuggestionsAsCodeFixes context sourceText)) |
Member
There was a problem hiding this comment.
🤖 🕵️ [P2] Choosing N.M.FixTarget817 rewrites M.FixTarget817() to M.N.M() (FS0039). Replace the matching long-identifier prefix, not just the diagnostic span.
module Review
module M = let marker = ()
module N =
module M =
type FixTarget817() = class end
let x = M.FixTarget817()
T-Gro
self-requested a review
September 10, 2026 12:33
The insertion point is a position, and the change was applied at the start
of its line. That is the same thing whenever what precedes the declaration
on that line is whitespace, and something else entirely when it is not:
(* header
*) let x = StringBuilder()
Here the scope's first declaration is on the line that closes the comment,
so the open went between `(* header` and `*)`, inside the comment, leaving
the name as unresolved as it was. Whether the old code got this right was
luck of the same kind: it snapped an implicit module to line 1 and so wrote
above the comment here, but wrote inside it when the comment closed on a
line of its own.
What precedes the declaration is trivia that has to stay where it is, so
the line is broken at the declaration instead, and the declaration keeps
its column - moving it to the start of the line would put the open the
implicit module now begins with offside of it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A plain `open` takes namespaces and F# modules. The code fix offered one for every candidate it found, so an unresolved `File` was answered with `open System.Net.WebRequestMethods` and an unresolved `WriteLine` with `open System.Console`, `open System.Diagnostics.Debug` and `open System.Diagnostics.Trace` — all of them code that does not compile, because those name types, and a type's nested types and static members are only brought into scope by `open type`. Nothing in the entity model told the two apart: the namespace to open was the dotted name of the candidate's container, whatever kind of thing that container was. `AssemblySymbol` now records how far a plain open reaches — the namespace, extended by each enclosing F# module — and `InsertionContextEntity` how much of the candidate's name the suggested open covers, which the qualification already written at the use site and auto-open modules can both shorten. The code fix compares the two and writes `open type` when the suggestion reaches past what a plain open can. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The fix computed a suggestion for every namespace and type a name could be resolved from, and then `Seq.tryHead` threw all but one away — whichever one the assembly crawler happened to reach first. An unresolved `File` was answered with `System.Net.WebRequestMethods` and never with `System.IO`; an unresolved `WriteLine` with `System.Diagnostics.Trace` and never with `System.Console`. `IFSharpMultiCodeFixProvider` already exists for providers with more than one suggestion, so the provider implements that instead and registers them all. Following Roslyn's add-import fix, which faces the same problem: the list is capped, at five opens and three qualifications, because a lightbulb is a menu somebody reads, and it is ordered with what `System` holds first rather than by whatever order the crawler walked the assemblies in -- without that, `Microsoft.VisualBasic.FileSystem` sorts above `System.Console` for `WriteLine`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The tests filtered the suggestions down to the opens, so what the fix offers for qualifying the name in place went unasserted — including that three of them is where the list stops. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
xperiandri
force-pushed
the
fix/add-open-type
branch
from
September 10, 2026 13:15
93b84ca to
f7e3ac6
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


Two defects in what the "open namespace" code fix suggests, both visible on the same example: an unresolved
FileinFile.ReadAllText "x".A plain
openwas offered for things it cannot reachA plain
opentakes namespaces and F# modules. The fix offered one for every candidate it found, soFilewas answered withopen System.Net.WebRequestMethodsand an unresolvedWriteLinewithopen System.Console— code that does not compile,FS0039: The namespace 'WebRequestMethods' is not defined, because those name types, and a type's nested types and static members are only brought into scope byopen type.Nothing in the entity model told the two apart: the namespace to open was the dotted name of the candidate's container, whatever kind of thing that container was.
AssemblySymbolnow records how far a plainopenreaches — the namespace, extended by each enclosing F# module — andInsertionContextEntityhow much of the candidate's name the suggestedopencovers, which the qualification already written at the use site and auto-open modules can both shorten. The fix compares the two and writesopen typewhen the suggestion reaches past what a plainopencan.Only one suggestion was ever offered
The fix computed a suggestion for every namespace and type a name could be resolved from, and then
Seq.tryHeadthrew all but one away — whichever one the assembly crawler happened to reach first.Filewas answered withSystem.Net.WebRequestMethodsand never withSystem.IO.IFSharpMultiCodeFixProvideralready exists for providers with more than one suggestion, so the provider implements that instead and registers them all. Following Roslyn'sAbstractAddImportCodeFixProvider, which faces the same problem:MaxResultsof 5 and its fully-qualify's 3 — because a lightbulb is a menu somebody reads;Systemholds first (Roslyn'splaceSystemNamespaceFirst: true) rather than by the order the crawler walked the assemblies in. Without that,Microsoft.VisualBasic.FileSystemsorts aboveSystem.ConsoleforWriteLine.Tests
AddOpenOnTopOnTests/AddOpenOnTopOffTestsnow assert the whole list of suggestions rather than one, with cases for a name reachable from two namespaces, a static member reachable from four types, and the qualifications offered after the opens.🤖 Generated with Claude Code