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:
// Reference F# compiler API
#r "FSharp.Compiler.Service.dll"
open System
open System.IO
open FSharp.Compiler.CodeAnalysis
open FSharp.Compiler.Symbols
open FSharp.Compiler.Text
// Create an interactive checker instance
let checker = FSharpChecker.Create()
We now perform type checking on the specified input:
let parseAndTypeCheckSingleFile (file, input) =
// Get context representing a stand-alone (script) file
let projOptions, errors =
checker.GetProjectOptionsFromScript(file, input, assumeDotNetFramework=false)
|> 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.
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, SourceText.ofString input2)
Now get the partial assembly signature for the code:
let partialAssemblySignature = checkFileResults.PartialAssemblySignature
partialAssemblySignature.Entities.Count = 1 // one entity
Now get the entity that corresponds to the module containing the code:
let moduleEntity = partialAssemblySignature.Entities.[0]
moduleEntity.DisplayName = "Test"
Now get the entity that corresponds to the type definition in the code:
let classEntity = moduleEntity.NestedEntities.[0]
Now get the value that corresponds to the function defined in the code:
let fnVal = moduleEntity.MembersFunctionsAndValues.[0]
Now look around at the properties describing the function value:
fnVal.Attributes.Count // 1
fnVal.CurriedParameterGroups.Count // 1
fnVal.CurriedParameterGroups.[0].Count // 2
fnVal.CurriedParameterGroups.[0].[0].Name // Some "x"
fnVal.CurriedParameterGroups.[0].[1].Name // Some "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.)
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:
argTy1.HasTypeDefinition
argTy1.TypeDefinition.IsFSharpAbbreviation // "int"
We can now look at the right-hand-side of the type abbreviation, which is the type int32:
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:
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:
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 attributes 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 entities 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.
let parseAndCheckScript (file, input) =
let projOptions, errors =
checker.GetProjectOptionsFromScript(file, input, assumeDotNetFramework=false)
|> Async.RunSynchronously
checker.ParseAndCheckProject(projOptions) |> Async.RunSynchronously
Now do it for a particular input:
let tmpFile = Path.ChangeExtension(System.IO.Path.GetTempFileName() , "fs")
File.WriteAllText(tmpFile, input2)
let projectResults = parseAndCheckScript(tmpFile, SourceText.ofString input2)
Now look at the results:
let assemblySig = projectResults.AssemblySignature
printfn $"#entities = {assemblySig.Entities.Count}" // 1
printfn $"namespace = {assemblySig.Entities.[0].Namespace}" // one entity
printfn $"entity name = {assemblySig.Entities.[0].DisplayName}" // "Tmp28D0"
printfn $"#members = {assemblySig.Entities.[0].MembersFunctionsAndValues.Count}" // 1
printfn $"member name = {assemblySig.Entities.[0].MembersFunctionsAndValues.[0].DisplayName}" // "foo"
namespace FSharp
--------------------
namespace Microsoft.FSharp
namespace FSharp
--------------------
namespace Microsoft.FSharp
--------------------
type FSharpAttribute = member Format: context: FSharpDisplayContext -> string member IsAttribute: unit -> bool member AttributeType: FSharpEntity with get member ConstructorArguments: IList<FSharpType * objnull> with get member IsUnresolved: bool with get member NamedArguments: IList<FSharpType * string * bool * objnull> with get member Range: range with get
<summary> Represents a custom attribute attached to F# source code or a compiler .NET component </summary>
<summary> Used to parse and check F# source code. </summary>
type Async = static member AsBeginEnd: computation: ('Arg -> Async<'T>) -> ('Arg * AsyncCallback * objnull -> 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<'T> -> Async<'T> + 1 overload 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: Async<'T option> seq -> Async<'T option> static member FromBeginEnd: beginAction: (AsyncCallback * objnull -> IAsyncResult) * endAction: (IAsyncResult -> 'T) * ?cancelAction: (unit -> unit) -> Async<'T> + 3 overloads static member FromContinuations: callback: (('T -> unit) * (exn -> unit) * (OperationCanceledException -> unit) -> unit) -> Async<'T> ...
--------------------
type Async<'T>
member FSharpChecker.ParseAndCheckFileInProject: fileName: string * fileVersion: int * sourceText: ISourceText * options: FSharpProjectOptions * ?userOpName: string -> Async<FSharpParseFileResults * FSharpCheckFileAnswer>
<summary> The result of calling TypeCheckResult including the possibility of abort and background compiler not caught up. </summary>
<summary> Success </summary>
<summary> Functions related to ISourceText objects </summary>
<summary> Creates an ISourceText object from the given string </summary>
<summary> Get a view of the contents of the assembly up to and including the file just checked </summary>
<summary> The (non-nested) module and type definitions in this signature </summary>
<summary>Gets the number of elements contained in the <see cref="T:System.Collections.Generic.ICollection`1" />.</summary>
<returns>The number of elements contained in the <see cref="T:System.Collections.Generic.ICollection`1" />.</returns>
<summary> Get the name of the type or module as displayed in F# code </summary>
<summary> Get the modules and types defined in a module, or the nested types of a type </summary>
<summary> Get the properties, events and methods of a type definitions, or the functions and values of a module </summary>
<summary> Custom attributes attached to the value. These contain references to other values (i.e. constructors in types). Mutable to fixup these value references after copying a collection of values. </summary>
<summary>List of list of parameters, where each nested item represents a defined parameter</summary>
<remarks> Typically, there is only one nested list. However, code such as 'f (a, b) (c, d)' contains two groups, each with two parameters. In that example, there is a list made up of two lists, each with a parameter. </remarks>
<summary> Get the declaration location of the member, function or value </summary>
<summary> The start line of the range </summary>
<summary> Get the name as presented in F# error messages and documentation </summary>
<summary> Get the enclosing entity for the definition </summary>
<summary> Get the declaration location for the type constructor </summary>
<summary> Get the typars of the member, function or value </summary>
<summary> Get a result indicating if this is a must-inline value </summary>
<summary> Indicates if this value or member is an F# active pattern </summary>
<summary> Indicates if this is a compiler generated value </summary>
<summary> Indicates if this is an abstract member? </summary>
<summary> Indicates if this is an extension member? </summary>
<summary> Indicates if this is a getter method for a property, or a use of a property in getter mode </summary>
<summary> Indicates if this is an implicit constructor? </summary>
<summary> Indicates if this is an instance member, when seen from F#? </summary>
<summary> Indicates if this is a member, including extension members? </summary>
<summary> Indicates if this is a module or member value </summary>
<summary> Indicates if this is a mutable value </summary>
<summary> Indicates if this is a setter method for a property, or a use of a property in setter mode </summary>
<summary> Indicates if this is an F# type function </summary>
<summary> Get the full type of the member, function or value when used as a first class value </summary>
<summary> Indicates if the type is a function type. The GenericArguments property returns the domain and range of the function type. </summary>
<summary> Get the generic arguments for a tuple type, a function type or a type constructed using a named entity </summary>
<summary> Get the type definition for a type </summary>
<summary> Indicates if the type is constructed using a named entity, including array and byref types </summary>
<summary> Indicates if the entity is a measure, type or exception abbreviation </summary>
<summary> Get the type abbreviated by an F# type abbreviation </summary>
<summary> Get the namespace containing the type or module, if any. Use 'None' for item not in a namespace. </summary>
<summary> Get the compiled name of the type or module, possibly with `n mangling. This is identical to LogicalName unless the CompiledName attribute is used. </summary>
<summary> Get the resolution of the ProjectOptions </summary>
<summary> The file name for the assembly, if any </summary>
member FSharpChecker.ParseAndCheckProject: options: FSharpProjectOptions * ?userOpName: string -> Async<FSharpCheckProjectResults>
<summary>Performs operations on <see cref="T:System.String" /> instances that contain file or directory path information. These operations are performed in a cross-platform manner.</summary>
<summary>Provides static methods for the creation, copying, deletion, moving, and opening of a single file, and aids in the creation of <see cref="T:System.IO.FileStream" /> objects.</summary>
File.WriteAllText(path: string, contents: ReadOnlySpan<char>) : unit
File.WriteAllText(path: string, contents: string, encoding: Text.Encoding) : unit
File.WriteAllText(path: string, contents: ReadOnlySpan<char>, encoding: Text.Encoding) : unit
<summary> Get a view of the overall signature of the assembly. Only valid to use if HasCriticalErrors is false. </summary>
F# Compiler Guide