F# Compiler Services

Compiler Services: Editor services

This tutorial demonstrates how to use the editor services provided by the F# compiler. This API is used to provide auto-complete, tool-tips, parameter info help, matching of brackets and other functions in F# editors including Visual Studio, Xamarin Studio and Emacs (see fsharpbindings project for more information). Similarly to the tutorial on using untyped AST, we start by getting the InteractiveChecker object.

NOTE: The FSharp.Compiler.Service API is subject to change when later versions of the nuget package are published

Type checking sample source code

As in the previous tutorial (using untyped AST), we start by referencing FSharp.Compiler.Service.dll, opening the relevant namespace and creating an instance of InteractiveChecker:

// Reference F# compiler API
#r "FSharp.Compiler.Service.dll"

open System
open Microsoft.FSharp.Compiler.SourceCodeServices

// Create an interactive checker instance 
let checker = FSharpChecker.Create()

As previously, we use GetProjectOptionsFromScriptRoot to get a context where the specified input is the only file passed to the compiler (and it is treated as a script file or stand-alone F# source code).

// Sample input as a multi-line string
let input = 
  open System

  let foo() = 
    let msg = String.Concat("Hello"," ","world")
    if true then 
      printfn "%s" msg.
// Split the input & define file name
let inputLines = input.Split('\n')
let file = "/home/user/Test.fsx"

let projOptions = 
    checker.GetProjectOptionsFromScript(file, input)
    |> Async.RunSynchronously

To perform type checking, we first need to parse the input using ParseFileInProject, which gives us access to the untyped AST. However, then we need to call CheckFileInProject to perform the full type checking. This function also requires the result of ParseFileInProject, so the two functions are often called together.

// Perform parsing  
let parseFileResults = 
    checker.ParseFileInProject(file, input, projOptions) 
    |> Async.RunSynchronously

Before we look at the interesting operations provided by TypeCheckResults, we need to run the type checker on a sample input. On F# code with errors, you would get some type checking result (but it may contain incorrectly "guessed" results).

// Perform type checking
let checkFileAnswer = 
    checker.CheckFileInProject(parseFileResults, file, 0, input, projOptions) 
    |> Async.RunSynchronously

Alternatively you can use ParseAndCheckFileInProject to check both in one step:

let parseResults2, checkFileAnswer2 = 
    checker.ParseAndCheckFileInProject(file, 0, input, projOptions) 
    |> Async.RunSynchronously

The function returns both the untyped parse result (which we do not use in this tutorial), but also a CheckFileAnswer value, which gives us access to all the interesting functionality...

let checkFileResults = 
    match checkFileAnswer with
    | FSharpCheckFileAnswer.Succeeded(res) -> res
    | res -> failwithf "Parsing did not finish... (%A)" res

Here, we type check a simple function that (conditionally) prints "Hello world". On the last line, we leave an additional dot in msg. so that we can get the completion list on the msg value (we expect to see various methods on the string type there).

Using type checking results

Let's now look at some of the API that is exposed by the TypeCheckResults type. In general, this is the type that lets you implement most of the interesting F# source code editor services.

Getting a tool tip

To get a tool tip, you can use GetToolTipTextAlternate method. The method takes a line number and character offset. Both of the numbers are zero-based. In the sample code, we want to get tooltip for the foo function that is defined on line 3 (line 0 is blank) and the letter f starts at index 7 (the tooltip would work anywhere inside the identifier).

In addition, the method takes a tag of token which is typically IDENT, when getting tooltip for an identifier (the other option lets you get tooltip with full assembly location when using #r "...").

// Get tag of the IDENT token to be used as the last argument
open Microsoft.FSharp.Compiler
let identToken = FSharpTokenTag.Identifier

// Get tool tip at the specified location
let tip = checkFileResults.GetToolTipTextAlternate(4, 7, inputLines.[1], ["foo"], identToken)
printfn "%A" tip

NOTE: GetToolTipTextAlternate is an alternative name for the old GetToolTipText. The old GetToolTipText was deprecated because it accepted zero-based line numbers. At some point it will be removed, and GetToolTipTextAlternate will be renamed back to GetToolTipText.

Aside from the location and token kind, the function also requires the current contents of the line (useful when the source code changes) and a Names value, which is a list of strings representing the current long name. For example to get tooltip for the Random identifier in a long name System.Random, you would use location somewhere in the string Random and you would pass ["System"; "Random"] as the Names value.

The returned value is of type ToolTipText which contains a discriminated union ToolTipElement. The union represents different kinds of tool tips that you can get from the compiler.

Getting auto-complete lists

The next method exposed by TypeCheckResults lets us perform auto-complete on a given location. This can be called on any identifier or in any scope (in which case you get a list of names visible in the scope) or immediately after . to get a list of members of some object. Here, we get a list of members of the string value msg.

To do this, we call GetDeclarationListInfo with the location of the . symbol on the last line (ending with printfn "%s" msg.). The offsets are one-based, so the location is 7, 23. We also need to specify a function that says that the text has not changed and the current identifer where we need to perform the completion.

// Get declarations (autocomplete) for a location
let decls = 
      (Some parseFileResults, 7, 23, inputLines.[6], [], "msg", fun _ -> false)
    |> Async.RunSynchronously

// Print the names of available items
for item in decls.Items do
    printfn " - %s" item.Name

NOTE: v is an alternative name for the old GetDeclarations. The old GetDeclarations was deprecated because it accepted zero-based line numbers. At some point it will be removed, and GetDeclarationListInfo will be renamed back to GetDeclarations.

When you run the code, you should get a list containing the usual string methods such as Substring, ToUpper, ToLower etc. The fourth argument of GetDeclarations, here ([], "msg"), specifies the context for the auto-completion. Here, we want a completion on a complete name msg, but you could for example use (["System"; "Collections"], "Generic") to get a completion list for a fully qualified namespace.

Getting parameter information

The next common feature of editors is to provide information about overloads of a method. In our sample code, we use String.Concat which has a number of overloads. We can get the list using GetMethods operation. As previously, this takes zero-indexed offset of the location that we are interested in (here, right at the end of the String.Concat identifier) and we also need to provide the identifier again (so that the compiler can provide up-to-date information when the source code changes):

// Get overloads of the String.Concat method
let methods = 
    checkFileResults.GetMethodsAlternate(5, 27, inputLines.[4], Some ["String"; "Concat"])
    |> Async.RunSynchronously

// Print concatenated parameter lists
for mi in methods.Methods do
    [ for p in mi.Parameters -> p.Display ]
    |> String.concat ", " 
    |> printfn "%s(%s)" methods.MethodName

The code uses the Display property to get the annotation for each parameter. This returns information such as arg0: obj or params args: obj[] or str0: string, str1: string. We concatenate the parameters and print a type annotation with the method name.

Asynchronous and immediate operations

You may have noticed that CheckFileInProject is an asynchronous operation. This indicates that type checking of F# code can take some time. The F# compiler performs the work in background (automatically) and when we call CheckFileInProject method, it returns an asynchronous operation.

There is also the CheckFileInProjectIfReady method. This returns immediately if the type checking operation can't be started immediately, e.g. if other files in the project are not yet type-checked. In this case, a background worker might choose to do other work in the meantime, or give up on type checking the file until the FileTypeCheckStateIsDirty event is raised.

The fsharpbinding project has more advanced example of handling the background work where all requests are sent through an F# agent. This may be a more appropriate for implementing editor support.


The CheckFileAnswer object contains other useful methods that were not covered in this tutorial. You can use it to get location of a declaration for a given identifier, additional colorization information (the F# 3.1 colorizes computation builder identifiers & query operators) and others.

Using the FSharpChecker component in multi-project, incremental and interactive editing situations may involve knowledge of the FSharpChecker operations queue and the FSharpChecker caches.

Finally, if you are implementing an editor support for an editor that cannot directly call .NET API, you can call many of the methods discussed here via a command line interface that is available in the FSharp.AutoComplete project.

namespace System
namespace Microsoft
namespace Microsoft.FSharp
namespace Microsoft.FSharp.Compiler
namespace Microsoft.FSharp.Compiler.SourceCodeServices
val checker : FSharpChecker

Full name: Editor.checker
type FSharpChecker
member CheckFileInProject : parsed:FSharpParseFileResults * filename:string * fileversion:int * source:string * options:FSharpProjectOptions * ?isResultObsolete:IsResultObsolete * ?textSnapshotInfo:obj -> Async<FSharpCheckFileAnswer>
member CheckFileInProjectIfReady : parsed:FSharpParseFileResults * filename:string * fileversion:int * source:string * options:FSharpProjectOptions * ?isResultObsolete:IsResultObsolete * ?textSnapshotInfo:obj -> Async<FSharpCheckFileAnswer option>
member CheckProjectInBackground : options:FSharpProjectOptions -> unit
member ClearLanguageServiceRootCachesAndCollectAndFinalizeAllTransients : unit -> unit
member Compile : argv:string [] -> FSharpErrorInfo [] * int
member Compile : ast:ParsedInput list * assemblyName:string * outFile:string * dependencies:string list * ?pdbFile:string * ?executable:bool * ?noframework:bool -> FSharpErrorInfo [] * int
member CompileToDynamicAssembly : otherFlags:string [] * execute:(TextWriter * TextWriter) option -> FSharpErrorInfo [] * int * Assembly option
member CompileToDynamicAssembly : ast:ParsedInput list * assemblyName:string * dependencies:string list * execute:(TextWriter * TextWriter) option * ?debug:bool * ?noframework:bool -> FSharpErrorInfo [] * int * Assembly option
member GetBackgroundCheckResultsForFileInProject : filename:string * options:FSharpProjectOptions -> Async<FSharpParseFileResults * FSharpCheckFileResults>
member GetBackgroundParseResultsForFileInProject : filename:string * options:FSharpProjectOptions -> Async<FSharpParseFileResults>
member GetProjectOptionsFromCommandLineArgs : projectFileName:string * argv:string [] * ?loadedTimeStamp:DateTime -> FSharpProjectOptions
member GetProjectOptionsFromScript : filename:string * source:string * ?loadedTimeStamp:DateTime * ?otherFlags:string [] * ?useFsiAuxLib:bool -> Async<FSharpProjectOptions>
member InvalidateAll : unit -> unit
member InvalidateConfiguration : options:FSharpProjectOptions -> unit
member KeepProjectAlive : options:FSharpProjectOptions -> Async<IDisposable>
member MatchBracesAlternate : filename:string * source:string * options:FSharpProjectOptions -> Async<(range * range) []>
member NotifyProjectCleaned : options:FSharpProjectOptions -> unit
member ParseAndCheckFileInProject : filename:string * fileversion:int * source:string * options:FSharpProjectOptions * ?isResultObsolete:IsResultObsolete * ?textSnapshotInfo:obj -> Async<FSharpParseFileResults * FSharpCheckFileAnswer>
member ParseAndCheckProject : options:FSharpProjectOptions -> Async<FSharpCheckProjectResults>
member ParseFileInProject : filename:string * source:string * options:FSharpProjectOptions -> Async<FSharpParseFileResults>
member TryGetRecentCheckResultsForFile : filename:string * options:FSharpProjectOptions * ?source:string -> (FSharpParseFileResults * FSharpCheckFileResults * int) option
member BeforeBackgroundFileCheck : IEvent<string>
member CurrentQueueLength : int
member FileChecked : IEvent<string>
member FileParsed : IEvent<string>
member private FrameworkImportsCache : FrameworkImportsCache
member ImplicitlyStartBackgroundWork : bool
member MaxMemory : int
member MaxMemoryReached : IEvent<unit>
member PauseBeforeBackgroundWork : int
member ProjectChecked : IEvent<string>
member private ReactorOps : IReactorOperations
member private ReferenceResolver : Resolver
member ImplicitlyStartBackgroundWork : bool with set
member MaxMemory : int with set
member PauseBeforeBackgroundWork : int with set
static member Create : ?projectCacheSize:int * ?keepAssemblyContents:bool * ?keepAllBackgroundResolutions:bool * ?msbuildEnabled:bool -> FSharpChecker
static member GlobalForegroundParseCountStatistic : int
static member GlobalForegroundTypeCheckCountStatistic : int

Full name: Microsoft.FSharp.Compiler.SourceCodeServices.FSharpChecker
static member FSharpChecker.Create : ?projectCacheSize:int * ?keepAssemblyContents:bool * ?keepAllBackgroundResolutions:bool * ?msbuildEnabled:bool -> FSharpChecker
val input : string

Full name: Editor.input
val inputLines : string []

Full name: Editor.inputLines
String.Split([<ParamArray>] separator: char []) : string []
String.Split(separator: string [], options: StringSplitOptions) : string []
String.Split(separator: char [], options: StringSplitOptions) : string []
String.Split(separator: char [], count: int) : string []
String.Split(separator: string [], count: int, options: StringSplitOptions) : string []
String.Split(separator: char [], count: int, options: StringSplitOptions) : string []
val file : string

Full name: Editor.file
val projOptions : FSharpProjectOptions

Full name: Editor.projOptions
member FSharpChecker.GetProjectOptionsFromScript : filename:string * source:string * ?loadedTimeStamp:DateTime * ?otherFlags:string [] * ?useFsiAuxLib:bool -> Async<FSharpProjectOptions>
Multiple items
type Async
static member AsBeginEnd : computation:('Arg -> Async<'T>) -> ('Arg * AsyncCallback * obj -> IAsyncResult) * (IAsyncResult -> 'T) * (IAsyncResult -> unit)
static member AwaitEvent : event:IEvent<'Del,'T> * ?cancelAction:(unit -> unit) -> Async<'T> (requires delegate and 'Del :> Delegate)
static member AwaitIAsyncResult : iar:IAsyncResult * ?millisecondsTimeout:int -> Async<bool>
static member AwaitTask : task:Task -> Async<unit>
static member AwaitTask : task:Task<'T> -> Async<'T>
static member AwaitWaitHandle : waitHandle:WaitHandle * ?millisecondsTimeout:int -> Async<bool>
static member CancelDefaultToken : unit -> unit
static member Catch : computation:Async<'T> -> Async<Choice<'T,exn>>
static member FromBeginEnd : beginAction:(AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromBeginEnd : arg:'Arg1 * beginAction:('Arg1 * AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromBeginEnd : arg1:'Arg1 * arg2:'Arg2 * beginAction:('Arg1 * 'Arg2 * AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromBeginEnd : arg1:'Arg1 * arg2:'Arg2 * arg3:'Arg3 * beginAction:('Arg1 * 'Arg2 * 'Arg3 * AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromContinuations : callback:(('T -> unit) * (exn -> unit) * (OperationCanceledException -> unit) -> unit) -> Async<'T>
static member Ignore : computation:Async<'T> -> Async<unit>
static member OnCancel : interruption:(unit -> unit) -> Async<IDisposable>
static member Parallel : computations:seq<Async<'T>> -> Async<'T []>
static member RunSynchronously : computation:Async<'T> * ?timeout:int * ?cancellationToken:CancellationToken -> 'T
static member Sleep : millisecondsDueTime:int -> Async<unit>
static member Start : computation:Async<unit> * ?cancellationToken:CancellationToken -> unit
static member StartAsTask : computation:Async<'T> * ?taskCreationOptions:TaskCreationOptions * ?cancellationToken:CancellationToken -> Task<'T>
static member StartChild : computation:Async<'T> * ?millisecondsTimeout:int -> Async<Async<'T>>
static member StartChildAsTask : computation:Async<'T> * ?taskCreationOptions:TaskCreationOptions -> Async<Task<'T>>
static member StartImmediate : computation:Async<unit> * ?cancellationToken:CancellationToken -> unit
static member StartWithContinuations : computation:Async<'T> * continuation:('T -> unit) * exceptionContinuation:(exn -> unit) * cancellationContinuation:(OperationCanceledException -> unit) * ?cancellationToken:CancellationToken -> unit
static member SwitchToContext : syncContext:SynchronizationContext -> Async<unit>
static member SwitchToNewThread : unit -> Async<unit>
static member SwitchToThreadPool : unit -> Async<unit>
static member TryCancelled : computation:Async<'T> * compensation:(OperationCanceledException -> unit) -> Async<'T>
static member CancellationToken : Async<CancellationToken>
static member DefaultCancellationToken : CancellationToken

Full name: Microsoft.FSharp.Control.Async

type Async<'T>

Full name: Microsoft.FSharp.Control.Async<_>
static member Async.RunSynchronously : computation:Async<'T> * ?timeout:int * ?cancellationToken:Threading.CancellationToken -> 'T
val parseFileResults : FSharpParseFileResults

Full name: Editor.parseFileResults
member FSharpChecker.ParseFileInProject : filename:string * source:string * options:FSharpProjectOptions -> Async<FSharpParseFileResults>
val checkFileAnswer : FSharpCheckFileAnswer

Full name: Editor.checkFileAnswer
member FSharpChecker.CheckFileInProject : parsed:FSharpParseFileResults * filename:string * fileversion:int * source:string * options:FSharpProjectOptions * ?isResultObsolete:IsResultObsolete * ?textSnapshotInfo:obj -> Async<FSharpCheckFileAnswer>
val parseResults2 : FSharpParseFileResults

Full name: Editor.parseResults2
val checkFileAnswer2 : FSharpCheckFileAnswer

Full name: Editor.checkFileAnswer2
member FSharpChecker.ParseAndCheckFileInProject : filename:string * fileversion:int * source:string * options:FSharpProjectOptions * ?isResultObsolete:IsResultObsolete * ?textSnapshotInfo:obj -> Async<FSharpParseFileResults * FSharpCheckFileAnswer>
val checkFileResults : FSharpCheckFileResults

Full name: Editor.checkFileResults
type FSharpCheckFileAnswer =
  | Aborted
  | Succeeded of FSharpCheckFileResults

Full name: Microsoft.FSharp.Compiler.SourceCodeServices.FSharpCheckFileAnswer
union case FSharpCheckFileAnswer.Succeeded: FSharpCheckFileResults -> FSharpCheckFileAnswer
val res : FSharpCheckFileResults
val res : FSharpCheckFileAnswer
val failwithf : format:Printf.StringFormat<'T,'Result> -> 'T

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.failwithf
val identToken : int

Full name: Editor.identToken
module FSharpTokenTag

from Microsoft.FSharp.Compiler.SourceCodeServices
val Identifier : int

Full name: Microsoft.FSharp.Compiler.SourceCodeServices.FSharpTokenTag.Identifier
val tip : Async<FSharpToolTipText>

Full name: Editor.tip
member FSharpCheckFileResults.GetToolTipTextAlternate : line:int * colAtEndOfNames:int * lineText:string * names:string list * tokenTag:int -> Async<FSharpToolTipText>
val printfn : format:Printf.TextWriterFormat<'T> -> 'T

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.printfn
val decls : FSharpDeclarationListInfo

Full name: Editor.decls
member FSharpCheckFileResults.GetDeclarationListInfo : ParsedFileResultsOpt:FSharpParseFileResults option * line:int * colAtEndOfPartialName:int * lineText:string * qualifyingNames:string list * partialName:string * ?hasTextChangedSinceLastTypecheck:(obj * Range.range -> bool) -> Async<FSharpDeclarationListInfo>
union case Option.Some: Value: 'T -> Option<'T>
val item : FSharpDeclarationListItem
property FSharpDeclarationListInfo.Items: FSharpDeclarationListItem []
property FSharpDeclarationListItem.Name: string
val methods : FSharpMethodGroup

Full name: Editor.methods
member FSharpCheckFileResults.GetMethodsAlternate : line:int * colAtEndOfNames:int * lineText:string * names:string list option -> Async<FSharpMethodGroup>
val mi : FSharpMethodGroupItem
property FSharpMethodGroup.Methods: FSharpMethodGroupItem []
val p : FSharpMethodGroupItemParameter
property FSharpMethodGroupItem.Parameters: FSharpMethodGroupItemParameter []
property FSharpMethodGroupItemParameter.Display: string
Multiple items
type String =
  new : value:char -> string + 7 overloads
  member Chars : int -> char
  member Clone : unit -> obj
  member CompareTo : value:obj -> int + 1 overload
  member Contains : value:string -> bool
  member CopyTo : sourceIndex:int * destination:char[] * destinationIndex:int * count:int -> unit
  member EndsWith : value:string -> bool + 2 overloads
  member Equals : obj:obj -> bool + 2 overloads
  member GetEnumerator : unit -> CharEnumerator
  member GetHashCode : unit -> int

Full name: System.String

String(value: nativeptr<char>) : unit
String(value: nativeptr<sbyte>) : unit
String(value: char []) : unit
String(c: char, count: int) : unit
String(value: nativeptr<char>, startIndex: int, length: int) : unit
String(value: nativeptr<sbyte>, startIndex: int, length: int) : unit
String(value: char [], startIndex: int, length: int) : unit
String(value: nativeptr<sbyte>, startIndex: int, length: int, enc: Text.Encoding) : unit
val concat : sep:string -> strings:seq<string> -> string

Full name: Microsoft.FSharp.Core.String.concat
property FSharpMethodGroup.MethodName: string
Fork me on GitHub