forked from dotnet/fsharp
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsymbols.fsx
233 lines (180 loc) · 7.55 KB
/
symbols.fsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
(**
---
title: Tutorial: Symbols
category: FSharp.Compiler.Service
categoryindex: 300
index: 400
---
*)
(*** hide ***)
#I "../../artifacts/bin/FSharp.Compiler.Service/Debug/netstandard2.0"
(**
Compiler Services: Working with symbols
============================================
This tutorial demonstrates how to work with symbols provided by the F# compiler. See also [project wide analysis](project.html)
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](typedtree.html).
*)
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"