Skip to content

Make Fix/Message/TAST range types independent of FSharp.Compiler.Service#288

Closed
Copilot wants to merge 3 commits intomainfrom
copilot/make-analyzer-api-independent
Closed

Make Fix/Message/TAST range types independent of FSharp.Compiler.Service#288
Copilot wants to merge 3 commits intomainfrom
copilot/make-analyzer-api-independent

Conversation

Copy link
Contributor

Copilot AI commented Feb 27, 2026

Binary coupling to FCS types in the public analyzer API (Fix, Message, TASTCollecting) means every analyzer must reference FSharp.Compiler.Service, blocking binary compatibility across different tooling hosts.

Changes

New SDK-owned position/range types

  • Position struct (Line, Column) and Range struct (FileName, Start, End, StartLine/Column/EndLine/Column)
  • Static helpers mirror FCS API shape — Position.mkPos and Range.mkRange — so existing call sites need minimal changes
  • Both types are self-contained with zero compiler dependencies

Fix and Message updated

Fix.FromRange and Message.Range now use FSharp.Analyzers.SDK.Range instead of FSharp.Compiler.Text.range. Analyzers that only produce diagnostics no longer need a reference to FCS at all.

TASTCollecting walker API updated

WalkCall and WalkNewRecord now receive exprRange: Range (SDK type). Conversion from FSharpExpr.Range happens inside the SDK via the new RangeConversions module.

RangeConversions module (public)

module RangeConversions =
    val ofFcsPos:   p: FSharp.Compiler.Text.pos   -> Position
    val ofFcsRange: r: FSharp.Compiler.Text.range -> Range

Bridge for analyzers that work directly with FCS types and need to produce SDK messages.

Sample updated

OptionAnalyzer now has zero direct FSharp.Compiler.Text imports — the typed-tree range flows through as an SDK Range end-to-end:

// Before
open FSharp.Compiler.Text
let state = ResizeArray<range>()
override _.WalkCall _ m _ _ _ (range: range) = state.Add range

// After — no FCS.Text open needed
let state = ResizeArray<Range>()
override _.WalkCall _ m _ _ _ (range: Range) = state.Add range

File ordering

FSharp.Analyzers.SDK.fsi/fs now compiles before TASTCollecting.fsi/fs so Position/Range are in scope for the TAST signatures.

Original prompt

This section details on the original issue you should resolve

<issue_title>Make F# Analyzer API independent of FCS?</issue_title>
<issue_description>
I've been thinking about the problem of binary compatibility for F# analyzers, in the context of trying to get F# analyzers available in all F# tooling.

This is a blocking problem for incorporation of analyzers into the Visual F# Tools. This applies even if those tools switch to FsAutoComplete - all analyzers must still be loadable into all future iterations of F# tooling.

The main issue is binary compatibility for the information being drawn from the FCS API (FSharpSymbol etc.). I am wondering if we might consider making this F# Analyzer API completely independent of FCS. When hosted, FSharpAutoComplete would provide necessary shims.

This would mean the F# Analyzer API would include abstract types like IFSharpSymbol, ISyntaxTree and so on. It would be a complete self-contained, zero-dependency facade over all the information analyzers typically require.

FCS could then take a dependency on this component (which I called FSharp.Compiler.Analyzers in the link above).

</issue_description>

<agent_instructions>Give this a good old college try</agent_instructions>

Comments on the Issue (you are @copilot in this section)

@baronfel Something like this would be great for another reason: incorporating some kind of stable view of the syntax nodes into an interface would make it easier for tools like sharp lab to bind to multiple versions of the compiler, which would make before and after analysis and comparisons much easier. It would potentially also support work in progress comparisons for interesting feature branches of compiler work. @dsyme It might, yes - it partly depends how often we revise the API, whether an analysis application can host mutiple versions (do we use naming `FSharp.Compiler.Analysis.v1.0`, `FSharp.Compiler.Analysis.v2.0` etc.), and who provides the implementations of the shims.

It's really a hard problem - the need for binary compat means the API would have to be really, really stable. But the nature of the FCS API means it gets enriched on every language update, even without taking into account bug fixes. Even yesterday I added more information for witness passing, for example.

  • If information is missing from the API (e.g. new language features are added or the SyntaxTree changes) then the API would also need to be revamped.

  • If we got it right I'd be ok with FCS taking a dependency on this and promising to always provide shims going forward, including progressing forward as the API is revised and providing backwards compat shims.

  • The API must serve as a binary-compatible view into FCS - there's no point having it if it's not going to be 100% binary compat forever - we may as well just depend on FCS in that case.

  • From an engineering point of view something like this should likely sit in dotnet/fsharp, at least if FCS takes a dependency on it and implements the shims.

@dsyme I'll link this discussion in the RFC (am happy to keep discussing here) @dsyme It's curious that one way to make the Analyzer API both binary compatible and simple and stable and extensible would be to transact all context information via an untyped format like JSON. Perhaps it would even be fast enough. (I'm not saying we should do that, I'm just thinking aloud)
type AnalyzerAttribute

type JsonResponse = string

// implemented by FCS
type ISourceText =
    abstract ... (same as ISourceText in F# compiler)

// implemented by FCS
type IAnalyzerContext =
    abstract ProjectFile: string
    abstract ProjectOptions: string[]
    abstract FileName: string
    abstract Source: ISourceText
    abstract GetSyntaxTree: unit -> Async<JsonResponse>
    abstract GetTypedTree: unit -> Async<JsonResponse>

type postion = string * int * int
type range = string * int * int * int * int

// implemented by analyzer
type IAnalyzer =
    abstract GetDiagnostics: IAnalyzerContext -> Async<JsonResponse>
    abstract GetDiagnosticFixes: IAnalyzerContext * position -> Async<JsonResponse>
    abstract GetQuickInfo: IAnalyzerContext * position -> Async<JsonResponse>
@dsyme My interest in this is partly for the shape checking tooling for DiffSharp, which roughly speaking needs nothing more than the compilation arguments and ISourceText - it's happy to do its own recompilation (though would be marginally more efficient if it sits directly on the FSharpExpr/FSharpSymbols)

The annoying thing here is that this kind of tooling ...


🔒 GitHub Advanced Security automatically protects Copilot coding agent pull requests. You can protect all pull requests by enabling Advanced Security for your repositories. Learn more about Advanced Security.

Copilot AI and others added 2 commits February 27, 2026 03:29
…d TAST API

Co-authored-by: TheAngryByrd <1490044+TheAngryByrd@users.noreply.github.com>
…cting

Co-authored-by: TheAngryByrd <1490044+TheAngryByrd@users.noreply.github.com>
Copilot AI changed the title [WIP] Make F# Analyzer API independent of FCS Make Fix/Message/TAST range types independent of FSharp.Compiler.Service Feb 27, 2026
@TheAngryByrd
Copy link
Member

didn't get far, but wanted to see what it would try

@Smaug123
Copy link
Contributor

@TheAngryByrd Are you interested in me trying this with Claude? I've wanted this feature for a long time.

@TheAngryByrd
Copy link
Member

Yeah, I’d very much wanna see what an LLM can to

@Smaug123
Copy link
Contributor

I think #289 is directionally correct; it may even be actually correct modulo some design decisions like "should we continue exposing a tree-walking style API for convenience of migration". Feel free not to read it for now though, because I haven't carefully reviewed it so it's still not far from being raw slop.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Make F# Analyzer API independent of FCS?

3 participants