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 member ConstructorArguments: IList<FSharpType * obj> member IsUnresolved: bool member NamedArguments: IList<FSharpType * string * bool * obj> member Range: range
<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 * 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<'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 * obj -> 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> 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>
File.WriteAllText(path: string, contents: string) : unit
File.WriteAllText(path: string, contents: ReadOnlySpan<char>, encoding: Text.Encoding) : unit
File.WriteAllText(path: string, contents: string, encoding: Text.Encoding) : unit
<summary> Get a view of the overall signature of the assembly. Only valid to use if HasCriticalErrors is false. </summary>