F# Compiler Services


Compiler Services: Working with symbols

This tutorial demonstrates how to work with symbols provided by the F# compiler. See also project wide analysis for information on symbol references.

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

As usual we start by referencing FSharp.Compiler.Service.dll, opening the relevant namespace and creating an instance of FSharpChecker:

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
9: 
// Reference F# compiler API
#r "FSharp.Compiler.Service.dll"

open System
open System.IO
open Microsoft.FSharp.Compiler.SourceCodeServices

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

We now perform type checking on the specified input:

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
let parseAndTypeCheckSingleFile (file, input) = 
    // Get context representing a stand-alone (script) file
    let projOptions = 
        checker.GetProjectOptionsFromScript(file, input)
        |> Async.RunSynchronously

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

    // Wait until type checking succeeds (or 100 attempts)
    match checkFileResults with
    | FSharpCheckFileAnswer.Succeeded(res) -> parseFileResults, res
    | res -> failwithf "Parsing did not finish... (%A)" res

let file = "/home/user/Test.fsx"

Getting resolved signature information about the file

After type checking a file, you can access the inferred signature of a project up to and including the checking of the given file through the PartialAssemblySignature property of the TypeCheckResults.

The full signature information is available for modules, types, attributes, members, values, functions, union cases, record types, units of measure and other F# language constructs.

The typed expression trees are also available, see typed tree tutorial.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
let input2 = 
      """
[<System.CLSCompliant(true)>]
let foo(x, y) = 
    let msg = String.Concat("Hello"," ","world")
    if true then 
        printfn "x = %d, y = %d" x y 
        printfn "%s" msg

type C() = 
    member x.P = 1
      """
let parseFileResults, checkFileResults = 
    parseAndTypeCheckSingleFile(file, input2)

Now get the partial assembly signature for the code:

1: 
2: 
3: 
4: 
let partialAssemblySignature = checkFileResults.PartialAssemblySignature
    
partialAssemblySignature.Entities.Count = 1  // one entity
    

Now get the entity that corresponds to the module containing the code:

1: 
2: 
3: 
let moduleEntity = partialAssemblySignature.Entities.[0]

moduleEntity.DisplayName = "Test"

Now get the entity that corresponds to the type definition in the code:

1: 
let classEntity = moduleEntity.NestedEntities.[0]

Now get the value that corresponds to the function defined in the code:

1: 
let fnVal = moduleEntity.MembersFunctionsAndValues.[0]

Now look around at the properties describing the function value. All fo the following evaluate to true:

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 
fnVal.Attributes.Count = 1
fnVal.CurriedParameterGroups.Count // 1
fnVal.CurriedParameterGroups.[0].Count // 2
fnVal.CurriedParameterGroups.[0].[0].Name // "x"
fnVal.CurriedParameterGroups.[0].[1].Name // "y"
fnVal.DeclarationLocation.StartLine // 3
fnVal.DisplayName // "foo"
fnVal.EnclosingEntity.DisplayName // "Test"
fnVal.EnclosingEntity.DeclarationLocation.StartLine // 1
fnVal.GenericParameters.Count // 0
fnVal.InlineAnnotation // FSharpInlineAnnotation.OptionalInline
fnVal.IsActivePattern // false
fnVal.IsCompilerGenerated // false
fnVal.IsDispatchSlot // false
fnVal.IsExtensionMember // false
fnVal.IsPropertyGetterMethod // false
fnVal.IsImplicitConstructor // false
fnVal.IsInstanceMember // false
fnVal.IsMember // false
fnVal.IsModuleValueOrMember // true
fnVal.IsMutable // false
fnVal.IsPropertySetterMethod // false
fnVal.IsTypeFunction // false

Now look at the type of the function if used as a first class value. (Aside: the CurriedParameterGroups property contains more information like the names of the arguments.)

1: 
2: 
3: 
4: 
5: 
6: 
7: 
fnVal.FullType // int * int -> unit
fnVal.FullType.IsFunctionType // int * int -> unit
fnVal.FullType.GenericArguments.[0] // int * int 
fnVal.FullType.GenericArguments.[0].IsTupleType // int * int 
let argTy1 = fnVal.FullType.GenericArguments.[0].GenericArguments.[0]

argTy1.TypeDefinition.DisplayName // int

OK, so we got an object representation of the type int * int -> unit, and we have seen the first 'int'. We can find out more about the type 'int' as follows, determining that it is a named type, which is an F# type abbreviation, type int = int32:

1: 
2: 
argTy1.HasTypeDefinition
argTy1.TypeDefinition.IsFSharpAbbreviation // "int"

We can now look at the right-hand-side of the type abbreviation, which is the type int32:

1: 
2: 
3: 
let argTy1b = argTy1.TypeDefinition.AbbreviatedType
argTy1b.TypeDefinition.Namespace // Some "Microsoft.FSharp.Core" 
argTy1b.TypeDefinition.CompiledName // "int32" 

Again we can now look through the type abbreviation type int32 = System.Int32 to get the full information about the type:

1: 
2: 
3: 
let argTy1c = argTy1b.TypeDefinition.AbbreviatedType
argTy1c.TypeDefinition.Namespace // Some "SystemCore" 
argTy1c.TypeDefinition.CompiledName // "Int32" 

The type checking results for a file also contain information extracted from the project (or script) options used in the compilation, called the ProjectContext:

1: 
2: 
3: 
4: 
5: 
6: 
7: 
let projectContext = checkFileResults.ProjectContext
    
for ass in projectContext.GetReferencedAssemblies() do
    match ass.FileName with 
    | None -> printfn "compilation referenced an assembly without a file" 
    | Some s -> printfn "compilation references assembly '%s'" s
    

Notes:

  • If incomplete code is present, some or all of the attirbutes may not be quite as expected.
  • If some assembly references are missing (which is actually very, very common), then 'IsUnresolved' may be true on values, members and/or entites related to external assemblies. You should be sure to make your code robust against IsUnresolved exceptions.

Getting symbolic information about whole projects

To check whole projects, create a checker, then call parseAndCheckScript. In this case, we just check the project for a single script. By specifying a different "projOptions" you can create a specification of a larger project.

1: 
2: 
3: 
4: 
5: 
6: 
let parseAndCheckScript (file, input) = 
    let projOptions = 
        checker.GetProjectOptionsFromScript(file, input)
        |> Async.RunSynchronously

    checker.ParseAndCheckProject(projOptions) |> Async.RunSynchronously

Now do it for a particular input:

1: 
2: 
3: 
4: 
let tmpFile = Path.ChangeExtension(System.IO.Path.GetTempFileName() , "fs")
File.WriteAllText(tmpFile, input2)

let projectResults = parseAndCheckScript(tmpFile, input2)

Now look at the results:

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
let assemblySig = projectResults.AssemblySignature
    
assemblySig.Entities.Count = 1  // one entity
assemblySig.Entities.[0].Namespace  // one entity
assemblySig.Entities.[0].DisplayName // "Tmp28D0"
assemblySig.Entities.[0].MembersFunctionsAndValues.Count // 1 
assemblySig.Entities.[0].MembersFunctionsAndValues.[0].DisplayName // "foo" 
    
namespace System
namespace System.IO
namespace Microsoft
namespace Microsoft.FSharp
namespace Microsoft.FSharp.Compiler
val checker : obj

Full name: Symbols.checker
val parseAndTypeCheckSingleFile : file:'a * input:'b -> 'c

Full name: Symbols.parseAndTypeCheckSingleFile
val file : 'a
val input : 'b
val projOptions : obj
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 Choice : computations:seq<Async<'T option>> -> Async<'T option>
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 : obj
val checkFileResults : obj
val failwithf : format:Printf.StringFormat<'T,'Result> -> 'T

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.failwithf
val file : string

Full name: Symbols.file
val input2 : string

Full name: Symbols.input2
val parseFileResults : obj

Full name: Symbols.parseFileResults
val checkFileResults : obj

Full name: Symbols.checkFileResults
val partialAssemblySignature : obj

Full name: Symbols.partialAssemblySignature
val moduleEntity : obj

Full name: Symbols.moduleEntity
val classEntity : obj

Full name: Symbols.classEntity
val fnVal : obj

Full name: Symbols.fnVal
val argTy1 : obj

Full name: Symbols.argTy1
val argTy1b : obj

Full name: Symbols.argTy1b
Multiple items
type CompiledNameAttribute =
  inherit Attribute
  new : compiledName:string -> CompiledNameAttribute
  member CompiledName : string

Full name: Microsoft.FSharp.Core.CompiledNameAttribute

--------------------
new : compiledName:string -> CompiledNameAttribute
val argTy1c : obj

Full name: Symbols.argTy1c
val projectContext : obj

Full name: Symbols.projectContext
val ass : obj
union case Option.None: Option<'T>
val printfn : format:Printf.TextWriterFormat<'T> -> 'T

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.printfn
union case Option.Some: Value: 'T -> Option<'T>
val s : string
val parseAndCheckScript : file:'a * input:'b -> 'c

Full name: Symbols.parseAndCheckScript
val tmpFile : string

Full name: Symbols.tmpFile
type Path =
  static val DirectorySeparatorChar : char
  static val AltDirectorySeparatorChar : char
  static val VolumeSeparatorChar : char
  static val InvalidPathChars : char[]
  static val PathSeparator : char
  static member ChangeExtension : path:string * extension:string -> string
  static member Combine : [<ParamArray>] paths:string[] -> string + 3 overloads
  static member GetDirectoryName : path:string -> string
  static member GetExtension : path:string -> string
  static member GetFileName : path:string -> string
  ...

Full name: System.IO.Path
Path.ChangeExtension(path: string, extension: string) : string
Path.GetTempFileName() : string
type File =
  static member AppendAllLines : path:string * contents:IEnumerable<string> -> unit + 1 overload
  static member AppendAllText : path:string * contents:string -> unit + 1 overload
  static member AppendText : path:string -> StreamWriter
  static member Copy : sourceFileName:string * destFileName:string -> unit + 1 overload
  static member Create : path:string -> FileStream + 3 overloads
  static member CreateText : path:string -> StreamWriter
  static member Decrypt : path:string -> unit
  static member Delete : path:string -> unit
  static member Encrypt : path:string -> unit
  static member Exists : path:string -> bool
  ...

Full name: System.IO.File
File.WriteAllText(path: string, contents: string) : unit
File.WriteAllText(path: string, contents: string, encoding: Text.Encoding) : unit
val projectResults : obj

Full name: Symbols.projectResults
val assemblySig : obj

Full name: Symbols.assemblySig
Fork me on GitHub