はじめに

先日行った勉強会にて、熊谷さんの発表にて「なぜdefer内で宣言前の変数が使えるのか」という疑問をSwiftのコードをコンパイルしながら中間言語を追って探っていくという内容がありました。

熊谷さんのスライドはこちら

そこでswiftcコマンドを知り、興味が湧いたので少し触ってみます。

環境

Xcode 7.3
Swift 2.2

swiftcコマンド

Swiftコードのコンパイルコマンドにswiftcというコマンドがあります。
普段、XcodeでiOSアプリを作って、Runボタンを押すだけなので、このswiftcコマンドには馴染みがありません。

このコマンドでどんなことができるのか、少しずつ調べてみたいと思います。

swiftc -help を見てみる

まずは、swiftc -helpでコマンドの使い方を見てみます。

OVERVIEW: Swift compiler

USAGE: swiftc [options]

MODES:
-dump-ast Parse and type-check input file(s) and dump AST(s)
-dump-parse Parse input file(s) and dump AST(s)
-dump-type-refinement-contexts
Type-check input file(s) and dump type refinement contexts(s)
-emit-assembly Emit assembly file(s) (-S)
-emit-bc Emit LLVM BC file(s)
-emit-executable Emit a linked executable
-emit-ir Emit LLVM IR file(s)
-emit-library Emit a linked library
-emit-object Emit object file(s) (-c)
-emit-sibgen Emit serialized AST + raw SIL file(s)
-emit-sib Emit serialized AST + canonical SIL file(s)
-emit-silgen Emit raw SIL file(s)
-emit-sil Emit canonical SIL file(s)
-parse Parse input file(s)
-print-ast Parse and type-check input file(s) and pretty print AST(s)

OPTIONS:
-application-extension Restrict code to those available for App Extensions
-assert-config Specify the assert_configuration replacement. Possible values are Debug, Release, Replacement.
-D Specifies one or more build configuration options
-embed-bitcode-marker Embed placeholder LLVM IR data as a marker
-embed-bitcode Embed LLVM IR bitcode as data
-emit-dependencies Emit basic Make-compatible dependencies files
-emit-module-path Emit an importable module to -emit-module Emit an importable module
-emit-objc-header-path Emit an Objective-C header file to -emit-objc-header Emit an Objective-C header file
-fixit-all Apply all fixits from diagnostics without any filtering
-fixit-code Get compiler fixits as code edits
-framework Specifies a framework which should be linked against
-F Add directory to framework search path
-gline-tables-only Emit minimal debug info for backtraces only
-gnone Don't emit debug info
-g Emit debug info
-help Display available options
-import-underlying-module
Implicitly imports the Objective-C half of a module
-I Add directory to the import search path
-j Number of commands to execute in parallel
-L Add directory to library link search path
-l Specifies a library which should be linked against
-module-cache-path
Specifies the Clang module cache path
-module-link-name
Library to link against when using this module
-module-name Name of the module to build
-nostdimport Don't search the standard library import path for modules
-num-threads Enable multi-threading and specify number of threads
-Onone Compile without any optimization
-Ounchecked Compile with optimizations and remove runtime safety checks
-output-file-map A file which specifies the location of outputs
-O Compile with optimizations
-o Write output to
-parse-as-library Parse the input file(s) as libraries, not scripts
-parse-sil Parse the input file as SIL code, not Swift source
-parseable-output Emit textual output in a parseable format
-profile-coverage-mapping
Generate coverage data for use with profiled execution counts
-profile-generate Generate instrumented code to collect execution counts
-save-temps Save intermediate compilation results
-sdk Compile against
-serialize-diagnostics Serialize diagnostics in a binary format
-suppress-warnings Suppress all warnings
-target-cpu Generate code for a particular CPU variant
-target Generate code for the given target
-version Print version information and exit
-v Show commands to run and use verbose output
-warnings-as-errors Treat warnings as errors
-whole-module-optimization
Optimize input files together instead of individually
-Xcc Pass to the C/C++/Objective-C compiler
-Xlinker Specifies an option which should be passed to the linker

-dump-ast, -dump-parseモードを使ってみる

実験的に次のコードを-dump-ast, -dump-parseオプションでそれぞれ実行してみます。

do {
    let value = 10
    let string = "String"
    print(value)
    print(string)
}

まずは、-dump-parseした結果のファイルを書き出してみます。

$ swiftc -dump-parse Test.swift 2>&1 | open -f

すると、下のようなファイルができます。

(source_file
  (top_level_code_decl
    (brace_stmt
      (do_stmt
        (brace_stmt
          (pattern_binding_decl
            (pattern_named 'value')
            (integer_literal_expr type='<null>' value=10))

          (var_decl "value" type='<null type>' let storage_kind=stored)

          (pattern_binding_decl
            (pattern_named 'string')
            (string_literal_expr type='<null>' encoding=utf8 value="String"))

          (var_decl "string" type='<null type>' let storage_kind=stored)

          (call_expr type='<null>'
            (unresolved_decl_ref_expr type='<null>' name=print specialized=no)
            (paren_expr type='<null>'
              (declref_expr type='<null>' decl=Test.(file).top-level code.value@Test.swift:10:9 specialized=yes)))
          (call_expr type='<null>'
            (unresolved_decl_ref_expr type='<null>' name=print specialized=no)
            (paren_expr type='<null>'
              (declref_expr type='<null>' decl=Test.(file).top-level code.string@Test.swift:11:9 specialized=yes))))))))

これは、Swiftのコンパイル処理の最初の処理にあたる、AST(Abstruct Syntax Tree:抽象構文木)の生成を行った結果になります。
ASTについては、別途調べようと思います。

では次に、-dump-astした結果のファイルを書き出してみます。

$ swiftc -dump-ast Test.swift 2>&1 | open -f

すると、以下のようなファイルができます。

(source_file
  (top_level_code_decl
    (brace_stmt
      (do_stmt
        (brace_stmt
          (pattern_binding_decl
            (pattern_named type='Int' 'value')
            (call_expr implicit type='Int' location=Test.swift:10:17 range=[Test.swift:10:17 - line:10:17] nothrow
              (constructor_ref_call_expr implicit type='(_builtinIntegerLiteral: Int2048) -> Int' location=Test.swift:10:17 range=[Test.swift:10:17 - line:10:17] nothrow
                (declref_expr implicit type='Int.Type -> (_builtinIntegerLiteral: Int2048) -> Int' location=Test.swift:10:17 range=[Test.swift:10:17 - line:10:17] decl=Swift.(file).Int.init(_builtinIntegerLiteral:) specialized=no)
                (type_expr implicit type='Int.Type' location=Test.swift:10:17 range=[Test.swift:10:17 - line:10:17] typerepr='Int'))
              (tuple_expr implicit type='(_builtinIntegerLiteral: Int2048)' location=Test.swift:10:17 range=[Test.swift:10:17 - line:10:17] names=_builtinIntegerLiteral
                (integer_literal_expr type='Int2048' location=Test.swift:10:17 range=[Test.swift:10:17 - line:10:17] value=10))))

          (var_decl "value" type='Int' access=private let storage_kind=stored)

          (pattern_binding_decl
            (pattern_named type='String' 'string')
            (call_expr implicit type='String' location=Test.swift:11:18 range=[Test.swift:11:18 - line:11:18] nothrow
              (constructor_ref_call_expr implicit type='(_builtinStringLiteral: RawPointer, byteSize: Word, isASCII: Int1) -> String' location=Test.swift:11:18 range=[Test.swift:11:18 - line:11:18] nothrow
                (declref_expr implicit type='String.Type -> (_builtinStringLiteral: RawPointer, byteSize: Word, isASCII: Int1) -> String' location=Test.swift:11:18 range=[Test.swift:11:18 - line:11:18] decl=Swift.(file).String.init(_builtinStringLiteral:byteSize:isASCII:) specialized=no)
                (type_expr implicit type='String.Type' location=Test.swift:11:18 range=[Test.swift:11:18 - line:11:18] typerepr='String'))
              (string_literal_expr type='(_builtinStringLiteral: Builtin.RawPointer, byteSize: Builtin.Word, isASCII: Builtin.Int1)' location=Test.swift:11:18 range=[Test.swift:11:18 - line:11:18] encoding=utf8 value="String")))

          (var_decl "string" type='String' access=private let storage_kind=stored)

          (call_expr type='()' location=Test.swift:12:5 range=[Test.swift:12:5 - line:12:16] nothrow
            (declref_expr type='(Any..., separator: String, terminator: String) -> ()' location=Test.swift:12:5 range=[Test.swift:12:5 - line:12:5] decl=Swift.(file).print(_:separator:terminator:) specialized=no)
            (tuple_shuffle_expr implicit type='(Any..., separator: String, terminator: String)' location=Test.swift:12:11 range=[Test.swift:12:10 - line:12:16] sourceIsScalar elements=[-2, -1, -1] variadic_sources=[0]
              (paren_expr type='Any' location=Test.swift:12:11 range=[Test.swift:12:10 - line:12:16]
                (erasure_expr implicit type='Any' location=Test.swift:12:11 range=[Test.swift:12:11 - line:12:11]
                  (declref_expr type='Int' location=Test.swift:12:11 range=[Test.swift:12:11 - line:12:11] decl=Test.(file).top-level code.value@Test.swift:10:9 specialized=no)))))
          (call_expr type='()' location=Test.swift:13:5 range=[Test.swift:13:5 - line:13:17] nothrow
            (declref_expr type='(Any..., separator: String, terminator: String) -> ()' location=Test.swift:13:5 range=[Test.swift:13:5 - line:13:5] decl=Swift.(file).print(_:separator:terminator:) specialized=no)
            (tuple_shuffle_expr implicit type='(Any..., separator: String, terminator: String)' location=Test.swift:13:11 range=[Test.swift:13:10 - line:13:17] sourceIsScalar elements=[-2, -1, -1] variadic_sources=[0]
              (paren_expr type='Any' location=Test.swift:13:11 range=[Test.swift:13:10 - line:13:17]
                (erasure_expr implicit type='Any' location=Test.swift:13:11 range=[Test.swift:13:11 - line:13:11]
                  (declref_expr type='String' location=Test.swift:13:11 range=[Test.swift:13:11 - line:13:11] decl=Test.(file).top-level code.string@Test.swift:11:9 specialized=no))))))))))

このファイルは、先ほどとは違ってIntStringといった型情報が入っています。
こちらはコンパイルの次のステップとして、型チェックを行った結果になります。

swiftc -helpでのそれぞれのモードの説明でのそのように書かれていますね。

-dump-ast Parse and type-check input file(s) and dump AST(s)
-dump-parse Parse input file(s) and dump AST(s)

下のスライドの「AST + Type Checker」というところまでを行ったことになります。

間違えた型のコードを-dump-astしてみる

前述までで、swiftc -dump-astでswiftコードのコンパイルの過程として型チェックを行っていることがわかりました。
では、下のように少しコードを変えて、Int型にString型の文字列リテラルを代入したコードを同じように-dump-astしてみます。

do {
    let value = 10
    let string: Int = "String"
    print(value)
    print(string)
}
$ swiftc -dump-ast Test.swift 2>&1 | open -f

さて、次のようなファイルが吐き出されました。

Test.swift:11:23: error: cannot convert value of type 'String' to specified type 'Int'
    let string: Int = "String"
                      ^~~~~~~~
(source_file
  (top_level_code_decl
    (brace_stmt
      (do_stmt
        (brace_stmt
          (pattern_binding_decl
            (pattern_named type='Int' 'value')
            (call_expr implicit type='Int' location=Test.swift:10:17 range=[Test.swift:10:17 - line:10:17] nothrow
              (constructor_ref_call_expr implicit type='(_builtinIntegerLiteral: Int2048) -> Int' location=Test.swift:10:17 range=[Test.swift:10:17 - line:10:17] nothrow
                (declref_expr implicit type='Int.Type -> (_builtinIntegerLiteral: Int2048) -> Int' location=Test.swift:10:17 range=[Test.swift:10:17 - line:10:17] decl=Swift.(file).Int.init(_builtinIntegerLiteral:) specialized=no)
                (type_expr implicit type='Int.Type' location=Test.swift:10:17 range=[Test.swift:10:17 - line:10:17] typerepr='Int'))
              (tuple_expr implicit type='(_builtinIntegerLiteral: Int2048)' location=Test.swift:10:17 range=[Test.swift:10:17 - line:10:17] names=_builtinIntegerLiteral
                (integer_literal_expr type='Int2048' location=Test.swift:10:17 range=[Test.swift:10:17 - line:10:17] value=10))))

          (var_decl "value" type='Int' access=private let storage_kind=stored)

          (pattern_binding_decl
            (pattern_typed type='Int'
              (pattern_named type='Int' 'string')
              (type_ident
                (component id='Int' bind=Swift.(file).Int)))
            (string_literal_expr type='<<error type>>' location=Test.swift:11:23 range=[Test.swift:11:23 - line:11:23] encoding=utf8 value="String"))

          (var_decl "string" type='Int' access=private let storage_kind=stored)

          (call_expr type='()' location=Test.swift:12:5 range=[Test.swift:12:5 - line:12:16] nothrow
            (declref_expr type='(Any..., separator: String, terminator: String) -> ()' location=Test.swift:12:5 range=[Test.swift:12:5 - line:12:5] decl=Swift.(file).print(_:separator:terminator:) specialized=no)
            (tuple_shuffle_expr implicit type='(Any..., separator: String, terminator: String)' location=Test.swift:12:11 range=[Test.swift:12:10 - line:12:16] sourceIsScalar elements=[-2, -1, -1] variadic_sources=[0]
              (paren_expr type='Any' location=Test.swift:12:11 range=[Test.swift:12:10 - line:12:16]
                (erasure_expr implicit type='Any' location=Test.swift:12:11 range=[Test.swift:12:11 - line:12:11]
                  (declref_expr type='Int' location=Test.swift:12:11 range=[Test.swift:12:11 - line:12:11] decl=Test.(file).top-level code.value@Test.swift:10:9 specialized=no)))))
          (call_expr type='()' location=Test.swift:13:5 range=[Test.swift:13:5 - line:13:17] nothrow
            (declref_expr type='(Any..., separator: String, terminator: String) -> ()' location=Test.swift:13:5 range=[Test.swift:13:5 - line:13:5] decl=Swift.(file).print(_:separator:terminator:) specialized=no)
            (tuple_shuffle_expr implicit type='(Any..., separator: String, terminator: String)' location=Test.swift:13:11 range=[Test.swift:13:10 - line:13:17] sourceIsScalar elements=[-2, -1, -1] variadic_sources=[0]
              (paren_expr type='Any' location=Test.swift:13:11 range=[Test.swift:13:10 - line:13:17]
                (erasure_expr implicit type='Any' location=Test.swift:13:11 range=[Test.swift:13:11 - line:13:11]
                  (declref_expr type='Int' location=Test.swift:13:11 range=[Test.swift:13:11 - line:13:11] decl=Test.(file).top-level code.string@Test.swift:11:9 specialized=no))))))))))

見事に型の違いをエラーとして出してくれていますね。

ちなみに、このコードで-dump-parseすると、型チェックをまだしていないので、当然エラーを出していないことも確認できます。

終わりに

今回は一旦ここまでにしますが、引き続き調べていきたいと思います。

参考

http://www.slideshare.net/tomohirokumagai54/swift-open-hours-fincwwdc
http://qiita.com/demmy/items/f08a65298d2f2caf1360