Compiler Services: Project Analysis
This tutorial demonstrates how you can analyze a whole project using services provided by the F# compiler.
NOTE: The FSharp.Compiler.Service API is subject to change when later versions of the nuget package are published.
Getting whole-project results
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 System.Collections.Generic
open FSharp.Compiler.CodeAnalysis
open FSharp.Compiler.Symbols
open FSharp.Compiler.Text
// Create an interactive checker instance
let checker = FSharpChecker.Create()
Here are our sample inputs:
module Inputs =
open System.IO
let base1 = Path.GetTempFileName()
let fileName1 = Path.ChangeExtension(base1, ".fs")
let base2 = Path.GetTempFileName()
let fileName2 = Path.ChangeExtension(base2, ".fs")
let dllName = Path.ChangeExtension(base2, ".dll")
let projFileName = Path.ChangeExtension(base2, ".fsproj")
let fileSource1 =
"""
module M
type C() =
member x.P = 1
let xxx = 3 + 4
let fff () = xxx + xxx
"""
File.WriteAllText(fileName1, fileSource1)
let fileSource2 =
"""
module N
open M
type D1() =
member x.SomeProperty = M.xxx
type D2() =
member x.SomeProperty = M.fff() + D1().P
// Generate a warning
let y2 = match 1 with 1 -> M.xxx
"""
File.WriteAllText(fileName2, fileSource2)
We use GetProjectOptionsFromCommandLineArgs to treat two files as a project:
let projectOptions =
let sysLib nm =
if System.Environment.OSVersion.Platform = System.PlatformID.Win32NT then
// file references only valid on Windows
System.Environment.GetFolderPath(System.Environment.SpecialFolder.ProgramFilesX86)
+ @"\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0\"
+ nm
+ ".dll"
else
let sysDir =
System.Runtime.InteropServices.RuntimeEnvironment.GetRuntimeDirectory()
let (++) a b = System.IO.Path.Combine(a, b)
sysDir ++ nm + ".dll"
let fsCore4300 () =
if System.Environment.OSVersion.Platform = System.PlatformID.Win32NT then
// file references only valid on Windows
System.Environment.GetFolderPath(System.Environment.SpecialFolder.ProgramFilesX86)
+ @"\Reference Assemblies\Microsoft\FSharp\.NETFramework\v4.0\4.3.0.0\FSharp.Core.dll"
else
sysLib "FSharp.Core"
checker.GetProjectOptionsFromCommandLineArgs(
Inputs.projFileName,
[| yield "--simpleresolution"
yield "--noframework"
yield "--debug:full"
yield "--define:DEBUG"
yield "--optimize-"
yield "--out:" + Inputs.dllName
yield "--doc:test.xml"
yield "--warn:3"
yield "--fullpaths"
yield "--flaterrors"
yield "--target:library"
yield Inputs.fileName1
yield Inputs.fileName2
let references =
[ sysLib "mscorlib"
sysLib "System"
sysLib "System.Core"
fsCore4300 () ]
for r in references do
yield "-r:" + r |]
)
Now check the entire project (using the files saved on disk):
let wholeProjectResults =
checker.ParseAndCheckProject(projectOptions)
|> Async.RunSynchronously
Now look at the errors and warnings:
wholeProjectResults.Diagnostics.Length // 1
wholeProjectResults.Diagnostics.[0]
.Message.Contains("Incomplete pattern matches on this expression") // yes it does
wholeProjectResults.Diagnostics.[0].StartLine
wholeProjectResults.Diagnostics.[0].EndLine
wholeProjectResults.Diagnostics.[0].StartColumn
wholeProjectResults.Diagnostics.[0].EndColumn
Now look at the inferred signature for the project:
[ for x in wholeProjectResults.AssemblySignature.Entities -> x.DisplayName ] // ["N"; "M"]
[ for x in
wholeProjectResults.AssemblySignature.Entities.[0]
.NestedEntities -> x.DisplayName ] // ["D1"; "D2"]
[ for x in
wholeProjectResults.AssemblySignature.Entities.[1]
.NestedEntities -> x.DisplayName ] // ["C"]
[ for x in
wholeProjectResults.AssemblySignature.Entities.[0]
.MembersFunctionsAndValues -> x.DisplayName ] // ["y"; "y2"]
You can also get all symbols in the project:
let rec allSymbolsInEntities (entities: IList<FSharpEntity>) =
[ for e in entities do
yield (e :> FSharpSymbol)
for x in e.MembersFunctionsAndValues do
yield (x :> FSharpSymbol)
for x in e.UnionCases do
yield (x :> FSharpSymbol)
for x in e.FSharpFields do
yield (x :> FSharpSymbol)
yield! allSymbolsInEntities e.NestedEntities ]
let allSymbols =
allSymbolsInEntities wholeProjectResults.AssemblySignature.Entities
After checking the whole project, you can access the background results for individual files in the project. This will be fast and will not involve any additional checking.
let backgroundParseResults1, backgroundTypedParse1 =
checker.GetBackgroundCheckResultsForFileInProject(Inputs.fileName1, projectOptions)
|> Async.RunSynchronously
You can now resolve symbols in each file:
let xSymbolUseOpt =
backgroundTypedParse1.GetSymbolUseAtLocation(9, 9, "", [ "xxx" ])
let xSymbolUse = xSymbolUseOpt.Value
let xSymbol = xSymbolUse.Symbol
You can find out more about a symbol by doing type checks on various symbol kinds:
let xSymbolAsValue =
match xSymbol with
| :? FSharpMemberOrFunctionOrValue as xSymbolAsVal -> xSymbolAsVal
| _ -> failwith "we expected this to be a member, function or value"
For each symbol, you can look up the references to that symbol:
let usesOfXSymbol =
wholeProjectResults.GetUsesOfSymbol(xSymbol)
You can iterate all the defined symbols in the inferred signature and find where they are used:
let allUsesOfAllSignatureSymbols =
[ for s in allSymbols do
let uses = wholeProjectResults.GetUsesOfSymbol(s)
yield s.ToString(), uses ]
You can also look at all the symbols uses in the whole project (including uses of symbols with local scope)
let allUsesOfAllSymbols =
wholeProjectResults.GetAllUsesOfAllSymbols()
You can also request checks of updated versions of files within the project (note that the other files in the project are still read from disk, unless you are using the FileSystem API):
let parseResults1, checkAnswer1 =
checker.ParseAndCheckFileInProject(Inputs.fileName1, 0, SourceText.ofString Inputs.fileSource1, projectOptions)
|> Async.RunSynchronously
let checkResults1 =
match checkAnswer1 with
| FSharpCheckFileAnswer.Succeeded x -> x
| _ -> failwith "unexpected aborted"
let parseResults2, checkAnswer2 =
checker.ParseAndCheckFileInProject(Inputs.fileName2, 0, SourceText.ofString Inputs.fileSource2, projectOptions)
|> Async.RunSynchronously
let checkResults2 =
match checkAnswer2 with
| FSharpCheckFileAnswer.Succeeded x -> x
| _ -> failwith "unexpected aborted"
Again, you can resolve symbols and ask for references:
let xSymbolUse2Opt =
checkResults1.GetSymbolUseAtLocation(9, 9, "", [ "xxx" ])
let xSymbolUse2 = xSymbolUse2Opt.Value
let xSymbol2 = xSymbolUse2.Symbol
let usesOfXSymbol2 =
wholeProjectResults.GetUsesOfSymbol(xSymbol2)
Or ask for all the symbols uses in the file (including uses of symbols with local scope)
let allUsesOfAllSymbolsInFile1 =
checkResults1.GetAllUsesOfAllSymbolsInFile()
Or ask for all the uses of one symbol in one file:
let allUsesOfXSymbolInFile1 =
checkResults1.GetUsesOfSymbolInFile(xSymbol2)
let allUsesOfXSymbolInFile2 =
checkResults2.GetUsesOfSymbolInFile(xSymbol2)
Analyzing multiple projects
If you have multiple F# projects to analyze which include references from some projects to others,
then the simplest way to do this is to build the projects and specify the cross-project references using
a -r:path-to-output-of-project.dll argument in the ProjectOptions. However, this requires the build
of each project to succeed, producing the DLL file on disk which can be referred to.
In some situations, e.g. in an IDE, you may wish to allow references to other F# projects prior to successful compilation to
a DLL. To do this, fill in the ProjectReferences entry in ProjectOptions, which recursively specifies the project
options for dependent projects. Each project reference still needs a corresponding -r:path-to-output-of-project.dll
command line argument in ProjectOptions, along with an entry in ProjectReferences.
The first element of each tuple in the ProjectReferences entry should be the DLL name, i.e. path-to-output-of-project.dll.
This should be the same as the text used in the -r project reference.
When a project reference is used, the analysis will make use of the results of incremental analysis of the referenced F# project from source files, without requiring the compilation of these files to DLLs.
To efficiently analyze a set of F# projects which include cross-references, you should populate the ProjectReferences correctly and then analyze each project in turn.
NOTE: Project references are disabled if the assembly being referred to contains type provider components - specifying the project reference will have no effect beyond forcing the analysis of the project, and the DLL will still be required on disk.
Summary
As you have seen, the ParseAndCheckProject lets you access results of project-wide analysis
such as symbol references. To learn more about working with symbols, see Symbols.
Using the FSharpChecker component in multi-project, incremental and interactive editing situations may involve knowledge of the FSharpChecker operations queue and the FSharpChecker caches.
namespace FSharp
--------------------
namespace Microsoft.FSharp
namespace FSharp
--------------------
namespace Microsoft.FSharp
--------------------
Represents a custom attribute attached to F# source code or a compiler .NET component
type FSharpAttribute =
member Format: context: FSharpDisplayContext -> string
member IsAttribute: unit -> bool
member AttributeType: FSharpEntity
member ConstructorArguments: IList<FSharpType * objnull>
member IsUnresolved: bool
member NamedArguments: IList<FSharpType * string * bool * objnull>
member Range: range
type FSharpChecker =
member CheckFileInProject:
parseResults: FSharpParseFileResults *
fileName: string *
fileVersion: int *
sourceText: ISourceText *
options: FSharpProjectOptions *
?userOpName: string ->
Async<FSharpCheckFileAnswer>
member ClearCache: options: FSharpProjectOptions seq * ?userOpName: string -> unit + 1 overload
member ClearLanguageServiceRootCachesAndCollectAndFinalizeAllTransients:
unit ->
unit
member Compile:
argv: string array *
?userOpName: string ->
Async<FSharpDiagnostic array * exn option>
member FindBackgroundReferencesInFile: fileName: string * options: FSharpProjectOptions * symbol: FSharpSymbol * ?canInvalidateProject: bool * ?fastCheck: bool * ?userOpName: string -> Async<range seq> + 1 overload
member GetBackgroundCheckResultsForFileInProject:
fileName: string *
options: FSharpProjectOptions *
?userOpName: string ->
Async<FSharpParseFileResults * FSharpCheckFileResults>
member GetBackgroundParseResultsForFileInProject:
fileName: string *
options: FSharpProjectOptions *
?userOpName: string ->
Async<FSharpParseFileResults>
member GetBackgroundSemanticClassificationForFile: fileName: string * options: FSharpProjectOptions * ?userOpName: string -> Async<SemanticClassificationView option> + 1 overload
member GetParsingOptionsFromCommandLineArgs: sourceFiles: string list * argv: string list * ?isInteractive: bool * ?isEditing: bool -> FSharpParsingOptions * FSharpDiagnostic list + 1 overload
member GetParsingOptionsFromProjectOptions:
options: FSharpProjectOptions ->
FSharpParsingOptions * FSharpDiagnostic list
...
?projectCacheSize: int *
?keepAssemblyContents: bool *
?keepAllBackgroundResolutions: bool *
?legacyReferenceResolver: LegacyReferenceResolver *
?tryGetMetadataSnapshot: FSharp.Compiler.AbstractIL.ILBinaryReader.ILReaderTryGetMetadataSnapshot *
?suggestNamesForErrors: bool *
?keepAllBackgroundSymbolUses: bool *
?enableBackgroundItemKeyStoreAndSemanticClassification: bool *
?enablePartialTypeChecking: bool *
?parallelReferenceResolution: bool *
?shareImportedAssemblies: bool *
?captureIdentifiersWhenParsing: bool *
?documentSource: DocumentSource *
?useTransparentCompiler: bool *
?transparentCompilerCacheSizes: TransparentCompiler.CacheSizes ->
FSharpChecker
type Path =
static member ChangeExtension: path: string * extension: string -> string
static member Combine: path1: string * path2: string -> string + 4 overloads
static member EndsInDirectorySeparator: path: ReadOnlySpan<char> -> bool + 1 overload
static member Exists: path: string -> bool
static member GetDirectoryName: path: ReadOnlySpan<char> -> ReadOnlySpan<char> + 1 overload
static member GetExtension: path: ReadOnlySpan<char> -> ReadOnlySpan<char> + 1 overload
static member GetFileName: path: ReadOnlySpan<char> -> ReadOnlySpan<char> + 1 overload
static member GetFileNameWithoutExtension: path: ReadOnlySpan<char> -> ReadOnlySpan<char> + 1 overload
static member GetFullPath: path: string -> string + 1 overload
static member GetInvalidFileNameChars: unit -> char array
...
type File =
static member AppendAllBytes: path: string * bytes: byte array -> unit + 1 overload
static member AppendAllBytesAsync: path: string * bytes: byte array * ?cancellationToken: CancellationToken -> Task + 1 overload
static member AppendAllLines: path: string * contents: string seq -> unit + 1 overload
static member AppendAllLinesAsync: path: string * contents: string seq * encoding: Encoding * ?cancellationToken: CancellationToken -> Task + 1 overload
static member AppendAllText: path: string * contents: ReadOnlySpan<char> -> unit + 3 overloads
static member AppendAllTextAsync: path: string * contents: ReadOnlyMemory<char> * encoding: Encoding * ?cancellationToken: CancellationToken -> Task + 3 overloads
static member AppendText: path: string -> StreamWriter
static member Copy: sourceFileName: string * destFileName: string -> unit + 1 overload
static member Create: path: string -> FileStream + 2 overloads
static member CreateSymbolicLink:
path: string *
pathToTarget: string ->
FileSystemInfo
...
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
type Environment =
static member Exit: exitCode: int -> unit
static member ExpandEnvironmentVariables: name: string -> string
static member FailFast: message: string -> unit + 1 overload
static member GetCommandLineArgs: unit -> string array
static member GetEnvironmentVariable: variable: string -> string + 1 overload
static member GetEnvironmentVariables: unit -> IDictionary + 1 overload
static member GetFolderPath: folder: SpecialFolder -> string + 1 overload
static member GetLogicalDrives: unit -> string array
static member SetEnvironmentVariable: variable: string * value: string -> unit + 1 overload
static member CommandLine: string
...
returns: The platform identifier and version number.
property Environment.OSVersion: OperatingSystem with get
returns: One of the PlatformID values.
property OperatingSystem.Platform: PlatformID with get
type PlatformID =
Literal>]
| Win32S = 0
Literal>]
| Win32Windows = 1
Literal>]
| Win32NT = 2
Literal>]
| WinCE = 3
Literal>]
| Unix = 4
Literal>]
| Xbox = 5
Literal>]
| MacOSX = 6
Literal>]
| Other = 7
Environment.GetFolderPath(folder: Environment.SpecialFolder, option: Environment.SpecialFolderOption) : string
type SpecialFolder =
Literal>]
| Desktop = 0
Literal>]
| Programs = 2
Literal>]
| MyDocuments = 5
Literal>]
| Personal = 5
Literal>]
| Favorites = 6
Literal>]
| Startup = 7
Literal>]
| Recent = 8
Literal>]
| SendTo = 9
Literal>]
| StartMenu = 11
Literal>]
| MyMusic = 13
...
type RuntimeEnvironment =
static member FromGlobalAccessCache: a: Assembly -> bool
static member GetRuntimeDirectory: unit -> string
static member GetRuntimeInterfaceAsIntPtr:
clsid: Guid *
riid: Guid ->
nativeint
static member GetRuntimeInterfaceAsObject: clsid: Guid * riid: Guid -> obj
static member GetSystemVersion: unit -> string
static member SystemConfigurationFile: string
IO.Path.Combine( paths: string array) : string
IO.Path.Combine(path1: string, path2: string) : string
IO.Path.Combine(path1: string, path2: string, path3: string) : string
IO.Path.Combine(path1: string, path2: string, path3: string, path4: string) : string
projectFileName: string *
argv: string array *
?loadedTimeStamp: DateTime *
?isInteractive: bool *
?isEditing: bool ->
FSharpProjectOptions
from Project
projectSnapshot: FSharpProjectSnapshot *
?userOpName: string ->
Async<FSharpCheckProjectResults>
member FSharpChecker.ParseAndCheckProject:
options: FSharpProjectOptions *
?userOpName: string ->
Async<FSharpCheckProjectResults>
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 and 'Del: not null)
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>
computation: Async<'T> *
?timeout: int *
?cancellationToken: Threading.CancellationToken ->
'T
property FSharpCheckProjectResults.Diagnostics: FSharp.Compiler.Diagnostics.FSharpDiagnostic array with get
returns: The total number of elements in all the dimensions of the Array; zero if there are no elements in the array.
property Array.Length: int with get
property FSharpCheckProjectResults.AssemblySignature: FSharpAssemblySignature with get
property FSharpAssemblySignature.Entities: IList<FSharpEntity> with get
property FSharpEntity.DisplayName: string with get
property FSharpMemberOrFunctionOrValue.DisplayName: string with get
T: The type of elements in the list.
type IList<'T> =
inherit ICollection<'T>
inherit seq<'T>
inherit IEnumerable
override IndexOf: item: 'T -> int
override Insert: index: int * item: 'T -> unit
override RemoveAt: index: int -> unit
member Item: 'T
type FSharpEntity =
inherit FSharpSymbol
member AsType: unit -> FSharpType
member GetPublicNestedEntities: unit -> FSharpEntity seq
member TryGetFullCompiledName: unit -> string option
member TryGetFullDisplayName: unit -> string option
member TryGetFullName: unit -> string option
member TryGetMembersFunctionsAndValues:
unit ->
IList<FSharpMemberOrFunctionOrValue>
member TryGetMetadataText: unit -> ISourceText option
member AbbreviatedType: FSharpType
member AccessPath: string
...
The subtype of the symbol may reveal further information and can be one of FSharpEntity, FSharpUnionCase
FSharpField, FSharpGenericParameter, FSharpStaticParameter, FSharpMemberOrFunctionOrValue, FSharpParameter,
or FSharpActivePatternCase.
type FSharpSymbol =
member GetEffectivelySameAsHash: unit -> int
member HasAttribute: unit -> bool
member IsAccessible: FSharpAccessibilityRights -> bool
member IsEffectivelySameAs: other: FSharpSymbol -> bool
member TryGetAttribute: unit -> FSharpAttribute option
abstract Accessibility: FSharpAccessibility
member Assembly: FSharpAssembly
abstract Attributes: IList<FSharpAttribute>
member DeclarationLocation: range option
member DisplayName: string
...
property FSharpEntity.MembersFunctionsAndValues: IList<FSharpMemberOrFunctionOrValue> with get
property FSharpEntity.UnionCases: IList<FSharpUnionCase> with get
This includes static fields, the 'val' bindings in classes and structs, and the value definitions in enums.
For classes, the list may include compiler generated fields implied by the use of primary constructors.
property FSharpEntity.FSharpFields: IList<FSharpField> with get
property FSharpEntity.NestedEntities: IList<FSharpEntity> with get
fileName: string *
options: FSharpProjectOptions *
?userOpName: string ->
Async<FSharpParseFileResults * FSharpCheckFileResults>
line: int *
colAtEndOfNames: int *
lineText: string *
names: string list ->
FSharpSymbolUse option
property FSharpSymbolUse.Symbol: FSharpSymbol with get
type FSharpMemberOrFunctionOrValue =
inherit FSharpSymbol
member FormatRichText: displayContext: FSharpDisplayContext -> RichText
member GetOverloads:
matchParameterNumber: bool ->
IList<FSharpMemberOrFunctionOrValue> option
member GetReturnTypeRichText:
displayContext: FSharpDisplayContext ->
RichText option
member GetValSignatureText:
displayContext: FSharpDisplayContext *
m: range ->
string option
member GetWitnessPassingInfo: unit -> (string * IList<FSharpParameter>) option
member TryGetFullCompiledOperatorNameIdents: unit -> string array option
member TryGetFullDisplayName: unit -> string option
override Accessibility: FSharpAccessibility
member ApparentEnclosingEntity: FSharpEntity option
...
symbol: FSharpSymbol *
?cancellationToken: Threading.CancellationToken ->
FSharpSymbolUse array
?cancellationToken: Threading.CancellationToken ->
FSharpSymbolUse array
fileName: string *
projectSnapshot: FSharpProjectSnapshot *
?userOpName: string ->
Async<FSharpParseFileResults * FSharpCheckFileAnswer>
member FSharpChecker.ParseAndCheckFileInProject:
fileName: string *
fileVersion: int *
sourceText: ISourceText *
options: FSharpProjectOptions *
?userOpName: string ->
Async<FSharpParseFileResults * FSharpCheckFileAnswer>
module SourceText
from FSharp.Compiler.Text
val ofString: string -> ISourceText
type FSharpCheckFileAnswer =
| Aborted
| Succeeded of FSharpCheckFileResults
union case FSharpCheckFileAnswer.Succeeded:
FSharpCheckFileResults ->
FSharpCheckFileAnswer
?cancellationToken: Threading.CancellationToken ->
FSharpSymbolUse seq
symbol: FSharpSymbol *
?relatedSymbolKinds: RelatedSymbolUseKind *
?cancellationToken: Threading.CancellationToken ->
FSharpSymbolUse array
F# Compiler Guide