[Swift] `!`(強制アンラップ)の正体はどこに…？

# はじめに

Swiftには`Optional`型がありますよね。
たとえば`Dictionary`の`subscript(key: Key) -> Value? { get set }`の返り値は`Optional<Value>`(その糖衣構文の`Value?`)になってますね。
値があるかどうか保証できない場合に`Optional`型で返すということは一般的です。

その`Optiona`型のインスタンスについて、自信を持って「値がある！」と言える場合には、強制アンラップをすることがあるかと思います：

```Swift:Sample.swift
let maybeInt: Int? = someFunction()
if maybeInt != nil {
  // 実際はOptional bindingを使うと思いますが、あくまで例ということで…
  print(maybeInt!)
}
```

この、**強制アンラップ(forced unwrap)をするときの演算子ってどこで定義されているんでしょう？**
それを探っていきましょう。


# 予想

最初は次のようなコードが標準ライブラリのどこかにあるのではないかと想像していました：

```Swift:予想.swift
postfix operator !

extension Optional {
  public static postfix func !(optional: Optional) -> Wrapped {
    guard let wrapped = optional else {
      fatalError("nilだよ。")
    }
    return wrapped
  }
}
```

でも、そんなコードはどこにもありませんでした。
上記のコードを実行しようとしてみれば分かると思いますが、
`error: postfix operator names starting with '?' or '!' are disallowed to avoid collisions with built-in unwrapping operators`
というエラーが表示されると思います。
つまり**Swiftのコードでは`!`から始まる演算子(もちろん「`!`だけ」も含む)を定義することはできない**のです。


# `!`の正体を探す

## 検証コード

検証のために次のようなコードを用意しました：

```Swift:force-unwrap.swift
let _ = Int?.none!
```

1行でクラッシュするコードです。
今回は`!`の正体を知りたいだけなのでこれで十分です。


## AST

とりあえずAST(抽象構文木)をダンプしてみましょう。
`swiftc -dump-ast force-unwrap.swift`を実行します。

```console:force-unwrap.swift.ast
(source_file "force-unwrap.swift"
  (top_level_code_decl range=[force-unwrap.swift:1:1 - line:1:18]
    (brace_stmt implicit range=[force-unwrap.swift:1:1 - line:1:18]
      (pattern_binding_decl range=[force-unwrap.swift:1:1 - line:1:18]
        (pattern_any type='Int')
        Original init:
        (optional_evaluation_expr implicit type='<null>'
          (force_value_expr type='Int' location=force-unwrap.swift:1:18 range=[force-unwrap.swift:1:9 - line:1:18]
            (dot_syntax_call_expr type='Int?' location=force-unwrap.swift:1:14 range=[force-unwrap.swift:1:9 - line:1:14] nothrow
              (declref_expr type='(Optional<Int>.Type) -> Optional<Int>' location=force-unwrap.swift:1:14 range=[force-unwrap.swift:1:14 - line:1:14] decl=Swift.(file).Optional.none [with (substitution_map generic_signature=<Wrapped> (substitution Wrapped -> Int))] function_ref=unapplied)
              (type_expr type='Int?.Type' location=force-unwrap.swift:1:9 range=[force-unwrap.swift:1:9 - line:1:12] typerepr='Int?'))))
        Processed init:
        (force_value_expr type='Int' location=force-unwrap.swift:1:18 range=[force-unwrap.swift:1:9 - line:1:18]
          (dot_syntax_call_expr type='Int?' location=force-unwrap.swift:1:14 range=[force-unwrap.swift:1:9 - line:1:14] nothrow
            (declref_expr type='(Optional<Int>.Type) -> Optional<Int>' location=force-unwrap.swift:1:14 range=[force-unwrap.swift:1:14 - line:1:14] decl=Swift.(file).Optional.none [with (substitution_map generic_signature=<Wrapped> (substitution Wrapped -> Int))] function_ref=unapplied)
            (type_expr type='Int?.Type' location=force-unwrap.swift:1:9 range=[force-unwrap.swift:1:9 - line:1:12] typerepr='Int?'))))
)))

```

なんか、もうここで「`!`がどこで定義されているか」の答えは出ちゃってる気がしますけどね。
`!`(という後置演算子)を見つけると、`force_value_expr`(強制アンラップした値を表す式)として解釈されるということのようです。
つまり**`!`はSwiftのコードで定義されている訳ではなく構文解析の段階で評価される**ということでした。

…というだけでは当たり前すぎる結果なので、もう少し掘り下げてみましょう。
だって、**「`nil`を強制アンラップしたらfatal errorが起きる」**という実装が見えてきていません。

まずパーサにおける`!`の解釈を辿るため、 https://github.com/apple/swift/blob/main/lib/Parse/ParseExpr.cpp を見てみましょう。
どうやら、まず、後置演算子の式は[`Parser::parseExprPostfix`という関数](https://github.com/apple/swift/blob/6f2dff9fdd9c50d431a41dcc23bd852990a63538/lib/Parse/ParseExpr.cpp#L1382)で処理されます。その関数は内部で[`Parser::parseExprPostfixSuffix`という関数](https://github.com/apple/swift/blob/6f2dff9fdd9c50d431a41dcc23bd852990a63538/lib/Parse/ParseExpr.cpp#L1078)を呼んでおり、[`!`を見つけると`SyntaxKind::ForcedValueExpr`というノードを作ります](https://github.com/apple/swift/blob/6f2dff9fdd9c50d431a41dcc23bd852990a63538/lib/Parse/ParseExpr.cpp#L1285)。
これがASTで見た`force_value_expr`のようです。

…？
`nil`を強制アンラップしたときのことは(当然と言えば当然ですが)構文解析の段階では全く触れられません。
「`nil`を強制アンラップしたらfatal errorが起きる」動作を探るため、次はSILを見てみましょう。


## SIL

SIL(Swift Intermediate Language)をダンプしてみましょう。
`swiftc -emit-sil force-unwrap.swift`の出番です。
長いので全部を見たい人だけ開いてみてください：

<details><summary>SILの出力結果</summary><div>

```console:force-unwrap.swift.sil
sil_stage canonical

import Builtin
import Swift
import SwiftShims

// main
sil @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
  %2 = metatype $@thin Optional<Int>.Type
  br bb1                                          // id: %3

bb1:                                              // Preds: bb0
  %4 = string_literal utf8 "main/force-unwrap.swift" // user: %13
  %5 = integer_literal $Builtin.Word, 23          // user: %17
  %6 = integer_literal $Builtin.Word, 18
  br bb2                                          // id: %7

bb2:                                              // Preds: bb1
  %8 = string_literal utf8 "Unexpectedly found nil while unwrapping an Optional value" // user: %10
  %9 = integer_literal $Builtin.Word, 57          // user: %12
  %10 = builtin "ptrtoint_Word"(%8 : $Builtin.RawPointer) : $Builtin.Word // user: %12
  %11 = integer_literal $Builtin.Int8, 2          // user: %12
  %12 = struct $StaticString (%10 : $Builtin.Word, %9 : $Builtin.Word, %11 : $Builtin.Int8) // user: %31
  %13 = builtin "ptrtoint_Word"(%4 : $Builtin.RawPointer) : $Builtin.Word // user: %17
  br bb3                                          // id: %14

bb3:                                              // Preds: bb2
  %15 = integer_literal $Builtin.Int8, 2          // user: %17
  br bb4                                          // id: %16

bb4:                                              // Preds: bb3
  %17 = struct $StaticString (%13 : $Builtin.Word, %5 : $Builtin.Word, %15 : $Builtin.Int8) // user: %31
  %18 = integer_literal $Builtin.Int64, 1         // user: %19
  %19 = struct $UInt (%18 : $Builtin.Int64)       // user: %31
  br bb5                                          // id: %20

bb5:                                              // Preds: bb4
  %21 = string_literal utf8 "Fatal error"         // user: %23
  %22 = integer_literal $Builtin.Word, 11         // user: %25
  %23 = builtin "ptrtoint_Word"(%21 : $Builtin.RawPointer) : $Builtin.Word // user: %25
  %24 = integer_literal $Builtin.Int8, 2          // user: %25
  %25 = struct $StaticString (%23 : $Builtin.Word, %22 : $Builtin.Word, %24 : $Builtin.Int8) // user: %31
  br bb6                                          // id: %26

bb6:                                              // Preds: bb5
  %27 = integer_literal $Builtin.Int32, 1         // user: %28
  %28 = struct $UInt32 (%27 : $Builtin.Int32)     // user: %31
  br bb7                                          // id: %29

bb7:                                              // Preds: bb6
  // function_ref _assertionFailure(_:_:file:line:flags:)
  %30 = function_ref @$ss17_assertionFailure__4file4line5flagss5NeverOs12StaticStringV_A2HSus6UInt32VtF : $@convention(thin) (StaticString, StaticString, StaticString, UInt, UInt32) -> Never // user: %31
  %31 = apply %30(%25, %12, %17, %19, %28) : $@convention(thin) (StaticString, StaticString, StaticString, UInt, UInt32) -> Never
  unreachable                                     // id: %32
} // end sil function 'main'

// _diagnoseUnexpectedNilOptional(_filenameStart:_filenameLength:_filenameIsASCII:_line:_isImplicitUnwrap:)
sil public_external [transparent] [serialized] @$ss30_diagnoseUnexpectedNilOptional14_filenameStart01_E6Length01_E7IsASCII5_line17_isImplicitUnwrapyBp_BwBi1_BwBi1_tF : $@convention(thin) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, Builtin.Word, Builtin.Int1) -> () {
// %0                                             // users: %46, %13
// %1                                             // users: %53, %20
// %2                                             // users: %47, %14
// %3                                             // users: %54, %21
// %4                                             // user: %5
bb0(%0 : $Builtin.RawPointer, %1 : $Builtin.Word, %2 : $Builtin.Int1, %3 : $Builtin.Word, %4 : $Builtin.Int1):
  cond_br %4, bb13, bb1                           // id: %5

bb1:                                              // Preds: bb0
  %6 = string_literal utf8 "Unexpectedly found nil while unwrapping an Optional value" // user: %8
  %7 = integer_literal $Builtin.Word, 57          // user: %12
  %8 = builtin "ptrtoint_Word"(%6 : $Builtin.RawPointer) : $Builtin.Word // user: %12
  br bb2                                          // id: %9

bb2:                                              // Preds: bb1
  %10 = integer_literal $Builtin.Int8, 2          // user: %12
  br bb3                                          // id: %11

bb3:                                              // Preds: bb2
  %12 = struct $StaticString (%8 : $Builtin.Word, %7 : $Builtin.Word, %10 : $Builtin.Int8) // user: %37
  %13 = builtin "ptrtoint_Word"(%0 : $Builtin.RawPointer) : $Builtin.Word // user: %20
  cond_br %2, bb5, bb4                            // id: %14

bb4:                                              // Preds: bb3
  %15 = integer_literal $Builtin.Int8, 0          // user: %16
  br bb6(%15 : $Builtin.Int8)                     // id: %16

bb5:                                              // Preds: bb3
  %17 = integer_literal $Builtin.Int8, 2          // user: %18
  br bb6(%17 : $Builtin.Int8)                     // id: %18

// %19                                            // user: %20
bb6(%19 : $Builtin.Int8):                         // Preds: bb5 bb4
  %20 = struct $StaticString (%13 : $Builtin.Word, %1 : $Builtin.Word, %19 : $Builtin.Int8) // user: %37
  %21 = builtin "zextOrBitCast_Word_Int64"(%3 : $Builtin.Word) : $Builtin.Int64 // user: %22
  %22 = struct $UInt (%21 : $Builtin.Int64)       // user: %37
  br bb7                                          // id: %23

bb7:                                              // Preds: bb6
  br bb8                                          // id: %24

bb8:                                              // Preds: bb7
  %25 = string_literal utf8 "Fatal error"         // user: %27
  %26 = integer_literal $Builtin.Word, 11         // user: %31
  %27 = builtin "ptrtoint_Word"(%25 : $Builtin.RawPointer) : $Builtin.Word // user: %31
  br bb9                                          // id: %28

bb9:                                              // Preds: bb8
  %29 = integer_literal $Builtin.Int8, 2          // user: %31
  br bb10                                         // id: %30

bb10:                                             // Preds: bb9
  %31 = struct $StaticString (%27 : $Builtin.Word, %26 : $Builtin.Word, %29 : $Builtin.Int8) // user: %37
  br bb11                                         // id: %32

bb11:                                             // Preds: bb10
  %33 = integer_literal $Builtin.Int32, 1         // user: %34
  %34 = struct $UInt32 (%33 : $Builtin.Int32)     // user: %37
  br bb12                                         // id: %35

bb12:                                             // Preds: bb11
  // function_ref _assertionFailure(_:_:file:line:flags:)
  %36 = function_ref @$ss17_assertionFailure__4file4line5flagss5NeverOs12StaticStringV_A2HSus6UInt32VtF : $@convention(thin) (StaticString, StaticString, StaticString, UInt, UInt32) -> Never // user: %37
  %37 = apply %36(%31, %12, %20, %22, %34) : $@convention(thin) (StaticString, StaticString, StaticString, UInt, UInt32) -> Never
  unreachable                                     // id: %38

bb13:                                             // Preds: bb0
  %39 = string_literal utf8 "Unexpectedly found nil while implicitly unwrapping an Optional value" // user: %41
  %40 = integer_literal $Builtin.Word, 68         // user: %45
  %41 = builtin "ptrtoint_Word"(%39 : $Builtin.RawPointer) : $Builtin.Word // user: %45
  br bb14                                         // id: %42

bb14:                                             // Preds: bb13
  %43 = integer_literal $Builtin.Int8, 2          // user: %45
  br bb15                                         // id: %44

bb15:                                             // Preds: bb14
  %45 = struct $StaticString (%41 : $Builtin.Word, %40 : $Builtin.Word, %43 : $Builtin.Int8) // user: %70
  %46 = builtin "ptrtoint_Word"(%0 : $Builtin.RawPointer) : $Builtin.Word // user: %53
  cond_br %2, bb17, bb16                          // id: %47

bb16:                                             // Preds: bb15
  %48 = integer_literal $Builtin.Int8, 0          // user: %49
  br bb18(%48 : $Builtin.Int8)                    // id: %49

bb17:                                             // Preds: bb15
  %50 = integer_literal $Builtin.Int8, 2          // user: %51
  br bb18(%50 : $Builtin.Int8)                    // id: %51

// %52                                            // user: %53
bb18(%52 : $Builtin.Int8):                        // Preds: bb17 bb16
  %53 = struct $StaticString (%46 : $Builtin.Word, %1 : $Builtin.Word, %52 : $Builtin.Int8) // user: %70
  %54 = builtin "zextOrBitCast_Word_Int64"(%3 : $Builtin.Word) : $Builtin.Int64 // user: %55
  %55 = struct $UInt (%54 : $Builtin.Int64)       // user: %70
  br bb19                                         // id: %56

bb19:                                             // Preds: bb18
  br bb20                                         // id: %57

bb20:                                             // Preds: bb19
  %58 = string_literal utf8 "Fatal error"         // user: %60
  %59 = integer_literal $Builtin.Word, 11         // user: %64
  %60 = builtin "ptrtoint_Word"(%58 : $Builtin.RawPointer) : $Builtin.Word // user: %64
  br bb21                                         // id: %61

bb21:                                             // Preds: bb20
  %62 = integer_literal $Builtin.Int8, 2          // user: %64
  br bb22                                         // id: %63

bb22:                                             // Preds: bb21
  %64 = struct $StaticString (%60 : $Builtin.Word, %59 : $Builtin.Word, %62 : $Builtin.Int8) // user: %70
  br bb23                                         // id: %65

bb23:                                             // Preds: bb22
  %66 = integer_literal $Builtin.Int32, 1         // user: %67
  %67 = struct $UInt32 (%66 : $Builtin.Int32)     // user: %70
  br bb24                                         // id: %68

bb24:                                             // Preds: bb23
  // function_ref _assertionFailure(_:_:file:line:flags:)
  %69 = function_ref @$ss17_assertionFailure__4file4line5flagss5NeverOs12StaticStringV_A2HSus6UInt32VtF : $@convention(thin) (StaticString, StaticString, StaticString, UInt, UInt32) -> Never // user: %70
  %70 = apply %69(%64, %45, %53, %55, %67) : $@convention(thin) (StaticString, StaticString, StaticString, UInt, UInt32) -> Never
  unreachable                                     // id: %71
} // end sil function '$ss30_diagnoseUnexpectedNilOptional14_filenameStart01_E6Length01_E7IsASCII5_line17_isImplicitUnwrapyBp_BwBi1_BwBi1_tF'

// _assertionFailure(_:_:file:line:flags:)
sil [noinline] [_semantics "programtermination_point"] @$ss17_assertionFailure__4file4line5flagss5NeverOs12StaticStringV_A2HSus6UInt32VtF : $@convention(thin) (StaticString, StaticString, StaticString, UInt, UInt32) -> Never



// Mappings from '#fileID' to '#filePath':
//   'main/force-unwrap.swift' => 'force-unwrap.swift'
```
</div></details>

SILの中身を見た方は分かると思いますが、ここに来ていきなり"Unexpectedly found nil while implicitly unwrapping an Optional value"というエラーメッセージを用意しています(`@main`の`bb2:`)。そして`nil`をアンラップしようとしたらクラッシュするようになっています。
どうやら、構文を解析したのちSILに変換する段階で「nilを強制アンラップしたらfatal errorが起きる」動作を挿入しているようです。
これは https://github.com/apple/swift/blob/main/lib/SILGen/SILGenConvert.cpp の中の [`SILGenFunction::emitPreconditionOptionalHasValue`という関数](https://github.com/apple/swift/blob/4faa3973ac6f1fbd188564597c326a48c7d6a588/lib/SILGen/SILGenConvert.cpp#L184)でおこなっています。たぶん。

そして実際のエラー処理は https://github.com/apple/swift/blob/main/stdlib/public/core/Optional.swift の中にあります。[`_diagnoseUnexpectedNilOptional`という関数](https://github.com/apple/swift/blob/6f2dff9fdd9c50d431a41dcc23bd852990a63538/stdlib/public/core/Optional.swift#L298)がその処理を担っているのです。

ということで、「`nil`を強制アンラップしたらfatal errorが起きる」は**周りまわって"Optional.swift"で定義されていた**のでした。


# おわりに

というわけで、`!`という強制アンラップの演算子を少しだけ掘り下げてみた記事でした。
実は**筆者はC++が読めない**ので、間違いがあったらコメントをお願いします。