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 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, errors = 
        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.DeclaringEntity.Value.DisplayName // "Test"
fnVal.DeclaringEntity.Value.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 assembly in projectContext.GetReferencedAssemblies() do
    match assembly.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, errors = 
        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
Multiple items
namespace FSharp

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

Full name: Symbols.checker
type FSharpChecker
member CheckFileInProject : parsed:FSharpParseFileResults * filename:string * fileversion:int * source:string * options:FSharpProjectOptions * ?textSnapshotInfo:obj * ?userOpName:string -> Async<FSharpCheckFileAnswer>
member CheckProjectInBackground : options:FSharpProjectOptions * ?userOpName:string -> unit
member ClearLanguageServiceRootCachesAndCollectAndFinalizeAllTransients : unit -> unit
member Compile : argv:string [] * ?userOpName:string -> Async<FSharpErrorInfo [] * int>
member Compile : ast:ParsedInput list * assemblyName:string * outFile:string * dependencies:string list * ?pdbFile:string * ?executable:bool * ?noframework:bool * ?userOpName:string -> Async<FSharpErrorInfo [] * int>
member CompileToDynamicAssembly : otherFlags:string [] * execute:(TextWriter * TextWriter) option * ?userOpName:string -> Async<FSharpErrorInfo [] * int * Assembly option>
member CompileToDynamicAssembly : ast:ParsedInput list * assemblyName:string * dependencies:string list * execute:(TextWriter * TextWriter) option * ?debug:bool * ?noframework:bool * ?userOpName:string -> Async<FSharpErrorInfo [] * int * Assembly option>
member GetBackgroundCheckResultsForFileInProject : filename:string * options:FSharpProjectOptions * ?userOpName:string -> Async<FSharpParseFileResults * FSharpCheckFileResults>
member GetBackgroundParseResultsForFileInProject : filename:string * options:FSharpProjectOptions * ?userOpName:string -> Async<FSharpParseFileResults>
member GetParsingOptionsFromCommandLineArgs : argv:string list * ?isInteractive:bool -> FSharpParsingOptions * FSharpErrorInfo list
member GetParsingOptionsFromCommandLineArgs : sourceFiles:string list * argv:string list * ?isInteractive:bool -> FSharpParsingOptions * FSharpErrorInfo list
member GetParsingOptionsFromProjectOptions : FSharpProjectOptions -> FSharpParsingOptions * FSharpErrorInfo list
member GetProjectOptionsFromCommandLineArgs : projectFileName:string * argv:string [] * ?loadedTimeStamp:DateTime * ?extraProjectInfo:obj -> FSharpProjectOptions
member GetProjectOptionsFromScript : filename:string * source:string * ?loadedTimeStamp:DateTime * ?otherFlags:string [] * ?useFsiAuxLib:bool * ?assumeDotNetFramework:bool * ?extraProjectInfo:obj * ?optionsStamp:int64 * ?userOpName:string -> Async<FSharpProjectOptions * FSharpErrorInfo list>
member InvalidateAll : unit -> unit
member InvalidateConfiguration : options:FSharpProjectOptions * ?startBackgroundCompileIfAlreadySeen:bool * ?userOpName:string -> unit
member KeepProjectAlive : options:FSharpProjectOptions * ?userOpName:string -> Async<IDisposable>
member MatchBraces : filename:string * source:string * options:FSharpParsingOptions * ?userOpName:string -> Async<(range * range) []>
member NotifyProjectCleaned : options:FSharpProjectOptions * ?userOpName:string -> Async<unit>
member ParseAndCheckFileInProject : filename:string * fileversion:int * source:string * options:FSharpProjectOptions * ?textSnapshotInfo:obj * ?userOpName:string -> Async<FSharpParseFileResults * FSharpCheckFileAnswer>
member ParseAndCheckProject : options:FSharpProjectOptions * ?userOpName:string -> Async<FSharpCheckProjectResults>
member ParseFile : filename:string * source:string * options:FSharpParsingOptions * ?userOpName:string -> Async<FSharpParseFileResults>
member StopBackgroundCompile : unit -> unit
member TokenizeFile : source:string -> FSharpTokenInfo [] []
member TokenizeLine : line:string * state:FSharpTokenizerLexState -> FSharpTokenInfo [] * FSharpTokenizerLexState
member TryGetRecentCheckResultsForFile : filename:string * options:FSharpProjectOptions * ?source:string * ?userOpName:string -> (FSharpParseFileResults * FSharpCheckFileResults * int) option
member WaitForBackgroundCompile : unit -> unit
member BeforeBackgroundFileCheck : IEvent<string * obj option>
member CurrentQueueLength : int
member FileChecked : IEvent<string * obj option>
member FileParsed : IEvent<string * obj option>
member private FrameworkImportsCache : FrameworkImportsCache
member ImplicitlyStartBackgroundWork : bool
member MaxMemory : int
member MaxMemoryReached : IEvent<unit>
member PauseBeforeBackgroundWork : int
member ProjectChecked : IEvent<string * obj option>
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 * ?legacyReferenceResolver:Resolver * ?tryGetMetadataSnapshot:ILReaderTryGetMetadataSnapshot -> FSharpChecker
static member GlobalForegroundParseCountStatistic : int
static member GlobalForegroundTypeCheckCountStatistic : int

Full name: FSharp.Compiler.SourceCodeServices.FSharpChecker
static member FSharpChecker.Create : ?projectCacheSize:int * ?keepAssemblyContents:bool * ?keepAllBackgroundResolutions:bool * ?legacyReferenceResolver:FSharp.Compiler.ReferenceResolver.Resolver * ?tryGetMetadataSnapshot:FSharp.Compiler.AbstractIL.ILBinaryReader.ILReaderTryGetMetadataSnapshot -> FSharpChecker
val parseAndTypeCheckSingleFile : file:string * input:string -> 'a * FSharpCheckFileResults

Full name: Symbols.parseAndTypeCheckSingleFile
val file : string
val input : string
val projOptions : FSharpProjectOptions
val errors : obj
member FSharpChecker.GetProjectOptionsFromScript : filename:string * source:string * ?loadedTimeStamp:DateTime * ?otherFlags:string [] * ?useFsiAuxLib:bool * ?assumeDotNetFramework:bool * ?extraProjectInfo:obj * ?optionsStamp:int64 * ?userOpName:string -> Async<FSharpProjectOptions * FSharpErrorInfo list>
Multiple items

--------------------
type Async<'T>

Full name: Microsoft.FSharp.Control.Async<_>
val parseFileResults : 'a
val checkFileResults : FSharpCheckFileAnswer
member FSharpChecker.ParseAndCheckFileInProject : filename:string * fileversion:int * source:string * options:FSharpProjectOptions * ?textSnapshotInfo:obj * ?userOpName:string -> Async<FSharpParseFileResults * FSharpCheckFileAnswer>
type FSharpCheckFileAnswer =
  | Aborted
  | Succeeded of FSharpCheckFileResults

Full name: 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 file : string

Full name: Symbols.file
val input2 : string

Full name: Symbols.input2
val parseFileResults : obj

Full name: Symbols.parseFileResults
val checkFileResults : FSharpCheckFileResults

Full name: Symbols.checkFileResults
val partialAssemblySignature : FSharpAssemblySignature

Full name: Symbols.partialAssemblySignature
property FSharpCheckFileResults.PartialAssemblySignature: FSharpAssemblySignature
property FSharpAssemblySignature.Entities: Collections.Generic.IList<FSharpEntity>
property Collections.Generic.ICollection.Count: int
val moduleEntity : FSharpEntity

Full name: Symbols.moduleEntity
property FSharpEntity.DisplayName: string
val classEntity : FSharpEntity

Full name: Symbols.classEntity
property FSharpEntity.NestedEntities: Collections.Generic.IList<FSharpEntity>
val fnVal : FSharpMemberOrFunctionOrValue

Full name: Symbols.fnVal
property FSharpEntity.MembersFunctionsAndValues: Collections.Generic.IList<FSharpMemberOrFunctionOrValue>
property FSharpMemberOrFunctionOrValue.Attributes: Collections.Generic.IList<FSharpAttribute>
property FSharpMemberOrFunctionOrValue.CurriedParameterGroups: Collections.Generic.IList<Collections.Generic.IList<FSharpParameter>>
property FSharpMemberOrFunctionOrValue.DeclarationLocation: FSharp.Compiler.Range.range
property FSharp.Compiler.Range.range.StartLine: int
property FSharpMemberOrFunctionOrValue.DisplayName: string
property FSharpMemberOrFunctionOrValue.DeclaringEntity: FSharpEntity option
property Option.Value: FSharpEntity
property FSharpEntity.DeclarationLocation: FSharp.Compiler.Range.range
property FSharpMemberOrFunctionOrValue.GenericParameters: Collections.Generic.IList<FSharpGenericParameter>
property FSharpMemberOrFunctionOrValue.InlineAnnotation: FSharpInlineAnnotation
property FSharpMemberOrFunctionOrValue.IsActivePattern: bool
property FSharpMemberOrFunctionOrValue.IsCompilerGenerated: bool
property FSharpMemberOrFunctionOrValue.IsDispatchSlot: bool
property FSharpMemberOrFunctionOrValue.IsExtensionMember: bool
property FSharpMemberOrFunctionOrValue.IsPropertyGetterMethod: bool
property FSharpMemberOrFunctionOrValue.IsImplicitConstructor: bool
property FSharpMemberOrFunctionOrValue.IsInstanceMember: bool
property FSharpMemberOrFunctionOrValue.IsMember: bool
property FSharpMemberOrFunctionOrValue.IsModuleValueOrMember: bool
property FSharpMemberOrFunctionOrValue.IsMutable: bool
property FSharpMemberOrFunctionOrValue.IsPropertySetterMethod: bool
property FSharpMemberOrFunctionOrValue.IsTypeFunction: bool
property FSharpMemberOrFunctionOrValue.FullType: FSharpType
property FSharpType.IsFunctionType: bool
property FSharpType.GenericArguments: Collections.Generic.IList<FSharpType>
val argTy1 : FSharpType

Full name: Symbols.argTy1
property FSharpType.TypeDefinition: FSharpEntity
property FSharpType.HasTypeDefinition: bool
property FSharpEntity.IsFSharpAbbreviation: bool
val argTy1b : FSharpType

Full name: Symbols.argTy1b
property FSharpEntity.AbbreviatedType: FSharpType
property FSharpEntity.Namespace: string option
property FSharpEntity.CompiledName: string
val argTy1c : FSharpType

Full name: Symbols.argTy1c
val projectContext : FSharpProjectContext

Full name: Symbols.projectContext
property FSharpCheckFileResults.ProjectContext: FSharpProjectContext
member FSharpProjectContext.GetReferencedAssemblies : unit -> FSharpAssembly list
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 parseAndCheckScript : file:string * input:string -> 'a

Full name: Symbols.parseAndCheckScript
member FSharpChecker.ParseAndCheckProject : options:FSharpProjectOptions * ?userOpName:string -> Async<FSharpCheckProjectResults>
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
union case ScopeKind.Namespace: ScopeKind
Fork me on GitHub