navigation

コンパイラサービス: シンボルの処理

このチュートリアルでは、F#コンパイラによって提供される シンボルの扱い方についてのデモを紹介します。 シンボルの参照に関する情報については プロジェクト全体の分析 も参考にしてください。

注意: 以下で使用しているAPIは試験的なもので、 最新のnugetパッケージの公開に伴って変更されることがあります。

これまでと同じく、 FSharp.Compiler.Service.dll への参照を追加した後、 適切な名前空間をオープンし、 FSharpChecker のインスタンスを作成します:

// F#コンパイラAPIへの参照
#r "FSharp.Compiler.Service.dll"

open System
open System.IO
open FSharp.Compiler.SourceCodeServices

// インタラクティブチェッカーのインスタンスを作成
let checker = FSharpChecker.Create()

そして特定の入力値に対して型チェックを行います:

let parseAndTypeCheckSingleFile (file, input) =
    // スタンドアロンの(スクリプト)ファイルを表すコンテキストを取得
    let projOptions, _errors =
        checker.GetProjectOptionsFromScript(file, input)
        |> Async.RunSynchronously

    let parseFileResults, checkFileResults =
        checker.ParseAndCheckFileInProject(file, 0, input, projOptions)
        |> Async.RunSynchronously

    // 型チェックが成功(あるいは100%に到達)するまで待機
    match checkFileResults with
    | FSharpCheckFileAnswer.Succeeded(res) -> parseFileResults, res
    | res -> failwithf "Parsing did not finish... (%A)" res

let file = "/home/user/Test.fsx"

ファイルに対する解決済みのシグネチャ情報を取得する

ファイルに対する型チェックが完了すると、 TypeCheckResultsPartialAssemblySignature プロパティを参照することにより、 チェック中の特定のファイルを含む、推論されたプロジェクトのシグネチャに アクセスすることができます。

モジュールや型、属性、メンバ、値、関数、共用体、レコード型、測定単位、 およびその他のF#言語要素に対する完全なシグネチャ情報が参照できます。

ただし型付き式ツリーに対する情報は(今のところ)この方法では利用できません。

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)

これでコードに対する部分的なアセンブリのシグネチャが取得できるようになります:

let partialAssemblySignature = checkFileResults.PartialAssemblySignature

partialAssemblySignature.Entities.Count = 1  // エンティティは1つ

そしてコードを含むモジュールに関連したエンティティを取得します:

let moduleEntity = partialAssemblySignature.Entities.[0]

moduleEntity.DisplayName = "Test"

そしてコード内の型定義に関連したエンティティを取得します:

let classEntity = moduleEntity.NestedEntities.[0]

そしてコード内で定義された関数に関連した値を取得します:

let fnVal = moduleEntity.MembersFunctionsAndValues.[0]

関数値に関するプロパティの値を確認してみましょう。

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

次に、この関数の型がファーストクラスの値として使用されているかどうかチェックします。 (ちなみに CurriedParameterGroups プロパティには引数の名前など、 より多くの情報も含まれています)

fnVal.FullType // int * int -> unit
fnVal.FullType.IsFunctionType // true
fnVal.FullType.GenericArguments.[0] // int * int
fnVal.FullType.GenericArguments.[0].IsTupleType // true
let argTy1 = fnVal.FullType.GenericArguments.[0].GenericArguments.[0]

argTy1.TypeDefinition.DisplayName // int

というわけで int * int -> unit という型を表現するオブジェクトが取得できて、 その1つめの 'int' を確認できたわけです。 また、以下のようにすると 'int' 型についてのより詳細な情報が取得でき、 それが名前付きの型であり、F#の型省略形 type int = int32 であることがわかります:

argTy1.HasTypeDefinition // true
argTy1.TypeDefinition.IsFSharpAbbreviation // true

型省略形の右辺、つまり int32 についてもチェックしてみましょう:

let argTy1b = argTy1.TypeDefinition.AbbreviatedType
argTy1b.TypeDefinition.Namespace // Some "Microsoft.FSharp.Core"
argTy1b.TypeDefinition.CompiledName // "int32"

そして再び型省略形 type int32 = System.Int32 から型に関する完全な情報が取得できます:

let argTy1c = argTy1b.TypeDefinition.AbbreviatedType
argTy1c.TypeDefinition.Namespace // Some "System"
argTy1c.TypeDefinition.CompiledName // "Int32"

ファイルに対する型チェックの結果には、 コンパイル時に使用されたプロジェクト(あるいはスクリプト)のオプションに関する ProjectContext と呼ばれる情報も含まれています:

let projectContext = checkFileResults.ProjectContext

for assembly in projectContext.GetReferencedAssemblies() do
    match assembly.FileName with
    | None -> printfn "コンパイル時にファイルの存在しないアセンブリを参照しました"
    | Some s -> printfn "コンパイル時にアセンブリ '%s' を参照しました" s

注意:

  • 不完全なコードが存在する場合、一部あるいはすべての属性が意図したとおりには 並ばないことがあります。
  • (実際には非常によくあることですが)一部のアセンブリが見つからない場合、 外部アセンブリに関連する値やメンバ、エンティティにおける 'IsUnresolved' が trueになることがあります。 IsUnresolvedによる例外に対処できるよう、堅牢なコードにしておくべきです。

プロジェクト全体に対するシンボル情報を取得する

プロジェクト全体をチェックする場合、チェッカーを作成した後に parseAndCheckScript を呼び出します。 今回の場合は単に1つのスクリプトだけが含まれたプロジェクトをチェックします。 異なる "projOptions" を指定すると、巨大なプロジェクトに対する設定を 構成することもできます。

let parseAndCheckScript (file, input) =
    let projOptions, errors =
        checker.GetProjectOptionsFromScript(file, input)
        |> Async.RunSynchronously

    let projResults =
        checker.ParseAndCheckProject(projOptions)
        |> Async.RunSynchronously

    projResults

そして特定の入力に対してこの関数を呼び出します:

let tmpFile = Path.ChangeExtension(System.IO.Path.GetTempFileName() , "fs")
File.WriteAllText(tmpFile, input2)

let projectResults = parseAndCheckScript(tmpFile, input2)

結果は以下の通りです:

let assemblySig = projectResults.AssemblySignature

assemblySig.Entities.Count = 1  // エンティティは1つ
assemblySig.Entities.[0].Namespace  // null
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
type FSharpChecker =
  member CheckFileInProject : parsed:FSharpParseFileResults * filename:string * fileversion:int * sourceText:ISourceText * options:FSharpProjectOptions * ?textSnapshotInfo:obj * ?userOpName:string -> Async<FSharpCheckFileAnswer>
  member CheckProjectInBackground : options:FSharpProjectOptions * ?userOpName:string -> unit
  member ClearCache : options:seq<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 FindBackgroundReferencesInFile : filename:string * options:FSharpProjectOptions * symbol:FSharpSymbol * ?canInvalidateProject:bool * ?userOpName:string -> Async<seq<range>>
  member GetBackgroundCheckResultsForFileInProject : filename:string * options:FSharpProjectOptions * ?userOpName:string -> Async<FSharpParseFileResults * FSharpCheckFileResults>
  ...
static member FSharpChecker.Create : ?projectCacheSize:int * ?keepAssemblyContents:bool * ?keepAllBackgroundResolutions:bool * ?legacyReferenceResolver:FSharp.Compiler.ReferenceResolver.Resolver * ?tryGetMetadataSnapshot:FSharp.Compiler.AbstractIL.ILBinaryReader.ILReaderTryGetMetadataSnapshot * ?suggestNamesForErrors:bool * ?keepAllBackgroundSymbolUses:bool * ?enableBackgroundItemKeyStoreAndSemanticClassification:bool -> FSharpChecker
val parseAndTypeCheckSingleFile : file:string * input:FSharp.Compiler.Text.ISourceText -> FSharpParseFileResults * FSharpCheckFileResults
val file : string
val input : FSharp.Compiler.Text.ISourceText
val projOptions : FSharpProjectOptions
val _errors : FSharpErrorInfo list
Multiple items
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 -> Async<unit>
  static member AwaitTask : task:Task<'T> -> Async<'T>
  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:seq<Async<'T option>> -> Async<'T option>
  static member FromBeginEnd : beginAction:(AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
  ...

--------------------
type Async<'T> =
static member Async.RunSynchronously : computation:Async<'T> * ?timeout:int * ?cancellationToken:Threading.CancellationToken -> 'T
val parseFileResults : FSharpParseFileResults
val checkFileResults : FSharpCheckFileAnswer
type FSharpCheckFileAnswer =
  | Aborted
  | Succeeded of FSharpCheckFileResults
union case FSharpCheckFileAnswer.Succeeded: FSharpCheckFileResults -> FSharpCheckFileAnswer
val res : FSharpCheckFileResults
val res : FSharpCheckFileAnswer
val failwithf : format:Printf.StringFormat<'T,'Result> -> 'T
val input2 : string
val checkFileResults : FSharpCheckFileResults
val partialAssemblySignature : FSharpAssemblySignature
val moduleEntity : FSharpEntity
val classEntity : FSharpEntity
val fnVal : FSharpMemberOrFunctionOrValue
val argTy1 : FSharpType
val argTy1b : FSharpType
val argTy1c : FSharpType
val projectContext : FSharpProjectContext
val assembly : FSharpAssembly
union case Option.None: Option<'T>
val printfn : format:Printf.TextWriterFormat<'T> -> 'T
union case Option.Some: Value: 'T -> Option<'T>
val s : string
val parseAndCheckScript : file:string * input:FSharp.Compiler.Text.ISourceText -> FSharpCheckProjectResults
val errors : FSharpErrorInfo list
val projResults : FSharpCheckProjectResults
val tmpFile : string
type Path =
  static val DirectorySeparatorChar : char
  static val AltDirectorySeparatorChar : char
  static val VolumeSeparatorChar : char
  static val PathSeparator : char
  static val InvalidPathChars : char[]
  static member ChangeExtension : path:string * extension:string -> string
  static member Combine : [<ParamArray>] paths:string[] -> string + 3 overloads
  static member EndsInDirectorySeparator : path:ReadOnlySpan<char> -> bool + 1 overload
  static member GetDirectoryName : path:string -> string + 1 overload
  static member GetExtension : path:string -> string + 1 overload
  ...
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 AppendAllLinesAsync : path:string * contents:IEnumerable<string> * ?cancellationToken:CancellationToken -> Task + 1 overload
  static member AppendAllText : path:string * contents:string -> unit + 1 overload
  static member AppendAllTextAsync : path:string * contents:string * ?cancellationToken:CancellationToken -> Task + 1 overload
  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 CreateText : path:string -> StreamWriter
  static member Decrypt : path:string -> unit
  static member Delete : path:string -> unit
  ...
File.WriteAllText(path: string, contents: string) : unit
File.WriteAllText(path: string, contents: string, encoding: Text.Encoding) : unit
val projectResults : FSharpCheckProjectResults
val assemblySig : FSharpAssemblySignature
union case ScopeKind.Namespace: ScopeKind