Swift Digest
SE-0539 | Swift Evolution

マクロがプロパティ初期化式に self アクセスを許可できるようにする

Enable Macros to Grant self Access for Property Initializers

Proposal
SE-0539
Authors
Nils Grabenhorst
Review Manager
Tony Allevato
Status
Active review (August 2–16, 2026)

このダイジェストはClaude Opus 4.7 / 4.8によって生成されたものです(License)。原文はこちら

01 何が問題だったのか

Swift には古くから lazy キーワードによる遅延初期化があります。lazy var は、初期化コストが高く必要になるまで初期化を避けたい場合や、初期化が型の初期化「後」に確定する値(self へのアクセスを含む)に依存する場合に便利です。

コンパイラは lazy var foo = initExpr() を、おおよそ次のように変換します。

  • 同じスコープに T? 型の private なバッキング変数を追加し、初期値を nil にする
  • 元のプロパティを computed property に変換する
  • get アクセサの中で、バッキング変数が nil のときに初期化式を評価する

このとき初期化式は、あたかもすでに get アクセサの中へ移動されたかのように型チェックされます。そのため、通常のプロパティ初期化式では使えない self やインスタンスメンバーに、lazy var の初期化式からはアクセスできます。

struct Earth {
    let mice = 21

    let noGood = mice * 2
    //           ^ 🛑 cannot use instance member 'mice' within property initializer; property initializers run before 'self' is available

    // ✅ initializer expression has access to `self`!
    lazy var theAnswer = mice * 2
}

同じような変換は、accessor マクロを書くことでも実現できます。たとえば初期化式を get アクセサへ移動する @Lazy マクロを考えられます。ところが現在のコンパイラは、マクロ展開後の実際の文脈に関係なく、プロパティ初期化式は eager に評価されると仮定します。そのため、self アクセスを必要とすると、lazy の場合とは違ってコンパイルエラーになってしまいます。

struct Earth {
    let mice = 21

    @Lazy
    var theAnswer = mice * 2
    //              ^ 🛑 cannot use instance member 'mice' within property initializer; property initializers run before 'self' is available
}

@Lazy は初期化式を get アクセサへ移動し、そこでは self アクセスが許されるため、このエラーは不必要に制限的です。同様の場面は、たとえば Observable なオブジェクトの初期化時に SwiftUI の environment values へアクセスしたい場合などにも生じます。

02 どのように解決されるのか

accessor マクロのロール宣言に、任意の initialization: パラメータを追加します。指定できる値は selfAvailable または selfUnavailable の 2 つで、省略時のデフォルトは selfUnavailable です。

  • selfUnavailable: これまでどおりの挙動で、プロパティ初期化式から self にアクセスできません。
  • selfAvailable: マクロが初期化式を self にアクセスできる文脈へ移動することを宣言します。

マクロ作者は、初期化式を self の使える文脈(get アクセサなど)へ再配置する場合に selfAvailable を指定します。デフォルトが selfUnavailable なので、既存のマクロの挙動は変わりません。

// `@Lazy` マクロの宣言
//
// 初期化式を self が使える文脈で用いることを約束する:
@attached(accessor, initialization: selfAvailable, names: named(get), named(set))
@attached(peer, names: prefixed(_))
public macro Lazy() = #externalMacro( ... )

// 利用:

struct Earth {
    let mice = 21

    // ✅ ここで self が使える:
    @Lazy var theAnswer = self.mice * 2
}

initialization: は accessor マクロ専用です。body マクロなど他のロールに付けたり、selfAvailable / selfUnavailable 以外の値を渡したりすると、それぞれエラーと fix-it が示されます。

なぜマクロ作者による宣言が必要なのか

初期化式は 2 回型チェックされます。1 回目はマクロ展開「前」の元の文脈で、2 回目はマクロ展開「後」の新しい文脈です。1 回目のチェックは型推論のために欠かせません。推論された型はマクロに渡され、展開結果を組み立てる際にしばしば必要になるからです。

@Lazy var foo = 42
//              ^ プロパティ初期化式を型チェックした結果、型は Int と推論される

@Lazy var foo: Int = 42
//             ^ マクロは推論された型 Int を参照できる

このように、型推論はマクロ展開より先に行う必要があります。つまり、1 回目のチェックの時点では、マクロが初期化式を最終的にどう使うかはまだ分かりません。たとえば init アクセサの中で使われるなら、初期化式は型の初期化時に評価され、self は使えません。この場合は self アクセスを禁じる現在の挙動が正しいことになります。一方 @Lazy のように get アクセサへ移動するなら self アクセスは妥当です。この違いは 1 回目のチェックの時点では判別できないため、マクロ作者が initialization: で明示的に宣言する、という設計になっています。

型チェックへの影響

初期化式を元の文脈でチェックする際、型チェッカーは initialization: selfAvailable を宣言した accessor マクロが付いているかを調べます。付いていれば、既存の lazy キーワードと同じく self アクセスを許可します。それ以外の場合はこれまでどおり、self アクセスをエラーとして診断します。

マクロ作者が selfAvailable と宣言しておきながら、実際には self アクセスが許されない文脈(init アクセサなど)へ初期化式を再配置した場合、1 回目のチェックは通ってしまいますが、展開後のコードに対する 2 回目のチェックで期待どおりエラーが診断されます。

// マクロ宣言:
@attached(accessor, initialization: selfAvailable, names: named(get), named(init))
@attached(peer, names: prefixed(_))
public macro NotLazy() = #externalMacro( ... )

// 利用:
struct Earth {
    let mice = 21
    @NotLazy var theAnswer = mice * 2
}

// 展開結果:
struct Earth {
    let mice = 21
    private var _theAnswer: Int
    var theAnswer: Int {
        @storageRestrictions(initializes: _theAnswer)
        init {
            _theAnswer = mice * 2
//                       ^ 🛑 cannot use instance member 'mice' within property initializer; property initializers run before 'self' is available
        }
        get { _theAnswer }
    }
}

なお、selfAvailable を既存のマクロに後から付けると、self を使うコードの意味が変わりうる(同名の static メンバーとインスタンスメンバーの取り違えなど)ソース非互換を招く可能性があります。このため、既存マクロを selfAvailable に切り替える際は、マクロの提供者が非互換の可能性を慎重に検討し、ドキュメント化することが求められます。