ストリーミングはほとんどのブラウザと
Developerアプリで視聴できます。
-
Swiftマクロの書き方
Swiftマクロを使用して、コードをより表現力豊かで読みやすくする方法を発見しましょう。マクロがどのように繰り返しのコードの記述を避けるのに役立つかを探求し、アプリ内での使用方法を学びましょう。マクロの構築要素を共有し、テスト方法を示し、マクロからのコンパイルエラーを生成する方法を説明します。
関連する章
- 1:15 - Overview
- 5:10 - Create a macro using Xcode's macro template
- 10:50 - Macro roles
- 11:40 - Write a SlopeSubset macro to define an enum subset
- 20:17 - Inspect the syntax tree structure in the debugger
- 24:35 - Add a macro to an Xcode project
- 27:05 - Emit error messages from a macro
- 30:12 - Generalize SlopeSubset to a generic EnumSubset macro
リソース
関連ビデオ
WWDC23
-
ダウンロード
♪♪
ボイラープレートコードの 書き込み好き? 誰も好きじゃない! これを解決するためにSwift 5.9で Swiftマクロが導入されました Swiftマクロはコンパイル時に コードを何度も生成し、 アプリのコードが 表現豊かで読みやすくします 私はAlex Hoppenといいます 今日はSwfitマクロの書き方を 紹介します マクロの仕組みの説明から 始めます
その後 Xcodeに移行し マクロの作成方法を ご覧ください
Xcodeで 最初のマクロを見た後 コードベースを簡素化する 役割を探求し そして 現在 取り組んでいるアプリの コード簡素化術を教えます
最後に マクロが特定の文脈で 適用できない際 エラーや警告をコンパイラに 伝える方法を紹介します
はじめましょう これは初年度の学生の 算術練習用の 計算リストです 左に結果を整数で 右に計算を文字列リテラルで タプル形式で示しています これは繰り返しで冗長であり 結果が計算と一致することを 保証できないため エラーのリスクもあります Swift 5.9のStringify Macroで 簡素化できます
このマクロは Xcodeのテンプレートにも 含まれているものです stringify Macroは 単一のパラメータとして受取ります コンパイル時にマクロが 前に見た様なタプルに展開し 計算と結果が一致することを 保証します どう機能するのか マクロ自体の定義を 見てみましょう
関数に似ていますよね stringifyMacroは 整数を 入力パラメータとして受信し 結果 (整数) と 計算 (文字列) を含む タプルを出力します マクロ式の引数が マクロのパラメータと一致せず または型チェックに合致しない場合 コンパイラはマクロ展開せず エラーを出力します
例えばこのマクロに 文字列リテラルを渡すと String は Int の引数の 型に変換できません とエラーを出力します Cのマクロとは異なり プリプロセッサの段階で 評価されます ただし このマクロを ジェネリックにする等 Swiftの関数で おなじみの機能を 使用することができます
このマクロは freestanding expression このマクロは 式の使用箇所で利用でき #stringify のように ハッシュ記号で示されます AttachedMacroは宣言を 拡張できるマクロです あとで説明します 引数がマクロのパラメータと 一致していることを確認後 コンパイラはマクロを展開します マクロ式を見てみましょう
マクロの展開を行うために 各マクロはコンパイラプラグインで 実装を定義します コンパイラはマクロ式全体の ソースコードを そのプラグインに送信します まずマクロプラグインは マクロのソースコードの SwiftSyntaxツリーを解析します このツリーはマクロの ソースコードを正確に 表現したもので マクロ処理の基盤となります
例えば stringifyMacroは ツリー内で マクロ展開式ノードとして 表されます この式はマクロ名が 'stringify' です 引数は2と3を適用した Infix演算子のプラスです SwiftMacroの大きな利点は マクロの実装自体が Swiftで書かれたプログラムであり 構文ツリーに対して 自由な変換を行えることです
この場合 以前に見た タプルを生成します 生成された構文ツリーを 再びソースコードを シリアル化し コンパイラに送信して マクロ式を展開された コードで置き換えます
素晴らしいですね! しかし実際のコードの見え方を 理解したいと思います Xcode の新しいマクロ テンプレートには 先程の stringifyMacroが 定義されています テンプレートを辿って マクロの定義展開の仕組及び マクロのテスト方法を探りましょう を選択し テンプレート を選択します
マクロの名前をWWDC としましょう
テンプレートで得られることとは? 以前に見たものと似たような #stringifyマクロの呼出も 含まれています "a + b" を パラメータとして受け取り 結果とそれを生成した コードを返します マクロが展開される結果を 知りたい場合は 右クリックをして を選択
まさに以前に見たものと同じです でもマクロの定義は? それを確認しましょう
前回のstringifyMacroを やや汎用化したバージョンがあります ジェネリックな型Tを 受け取れるようになったのは このマクロです
マクロは外部マクロとして 宣言されています
これによりコンパイラは展開を行う為に WWDCマクロモジュール内の StringifyMacro型を 参照する必要があることを 示しています
その型はどう定義されるのか
詳しく見てみましょう StringifyMacro型は ExpressionMacroプロトコルに 準拠しています
このプロトコルには 1つの要件があります expansion 関数です expansion 関数は マクロ式の構文Treeと コンパイラとの通信に使用できる コンテキストを受け取ります
expansion 関数は 書換えた構文を返します
実装では何を行うのか説明します 実装ではマクロ式の単一の引数を 取得します 実装でstringifyが単一の パラメータを受け取ると宣言されており マクロ展開が適用される前に 引数が型チェックを通過する 必要があるため この引数の存在を 把握しています 文字列補間を使用して タプルの構文Treeを作成します 最初の要素は 引数そのものであり 2番目の要素は 引数のソースコードを含む 文字列リテラルです
ここで関数は文字列を 返していません それは式の構文を 返しています マクロはこのリテラルを 自動的に Swift パーサーによって 構文Treeに変換します そして2番目の引数に リテラル補間スタイルを 使用しているため リテラルの内容が適切に エスケープする事を保証します バグは嫌ですよね しかし更に嫌なのは マクロを展開することを 示的に要求しない限り 見えないコードのバグです マクロは適切にテストされることが 重要です マクロは副作用がなく 構文Treeのソースコードは 比較しやすいため ユニットテストを 書くのが理想的です テンプレートは既に1つの ユニットテストを含みます
テストケースでは assertMacroExpansionの 関数を使用して stringifyMacroの展開を 確認します
先程見た #stringify(a + b) の式を 入力として受け取ります そして Macroが展開された後に 'a + b' という要素と 文字列リテラル'a + b' を含む タプルを生成することを アサートします
テストケースにマクロを 展開する方法を伝えるために 'testMacros' パラメータが渡され #stringify は StringifyMacro型を使用して 展開されることが指定されます アプリのテストを実行して テストに合格できるか 確認しましょう
テストに合格しました 最初のマクロを作成できました
これまでに基本的な構成要素を 見てきました マクロ宣言はマクロのシグネチャを 定義します 加えてマクロの役割を宣言します コンパイラプラグインは マクロの展開を実行します それはSwiftで書かれた プログラムであり SwiftSyntaxツリー上で 動作します
またマクロは構文Treeの 決定論的な変換であるため テストが非常に容易で 構文Treeのソースコードは 比較しやすいため テストが行いやすいです 他のマクロの使用例とは? freestanding のマクロで見てきました マクロはハッシュ記号で始まることで マクロ式全体を書き換えることが できます declaration roleは 式ではなく 宣言を展開する freestanding の役割もある 他の種類は attachedMacrosです これらは属性(attribute) 同様に"@"で表記され マクロが添付された宣言を 拡張することができます 例えば attached member Macrosは 添付された型に新しい メンバーを追加できます 他の役割について 詳しく学ぶには "Expand on Swift macros" をご覧ください。 attached member に 焦点を当ててみましょう 私が取り組むアプリの コードベースを 改善するのに役立ちました 私はスキーの インストラクターでもあり 最近は生徒を連れて行く ツアーを計画する為の アプリを開発しています
避けるべき事は初心者を 難しすぎるコースに 連れて行くことです Swift型システムを使用し 強制したいと思います 好きなスキーリゾートの斜面を Slope列挙型に加え 初心者向けの斜面のみを含む EasySlope型を追加しています 初心者向け斜面に変換する イニシャライザと 簡単な斜面をEasySlopeに 変換するイニシャライザと 通常の斜面に戻す 計算プロパティを持っています
安全性を確保しながらも 繰り返し作業が多いです もし簡単な斜面を 追加したい場合
それをSlope EasySlope イニシャライザ そして計算プロパティに それらを追加する必要があります マクロを使用して改善する 方法を見てみましょう 自動的にイニシャライザと 計算プロパティを 生成することを目指します その方法とは イニシャライザと 計算プロパティは EasySlope型の メンバーなので attached member macroを 宣言する必要があります
次にマクロ実装を含む コンパイラプラグイン作成
テスト駆動開発の手法に 基づいてマクロの動作を 正しく確認するために開発します それをテストする為の テストケースを書くまで 実装を空のままにしておきます
テストケースでマクロの 振る舞いを定義した後 そのテストケースに 一致するように実装を書きます
そして新しいマクロを アプリに統合します うまくいけば イニシャライザを削除して マクロが代わりに 生成できるようになります
前に作成した テンプレートを使用します 実際に #stringifyマクロは アプリに必要ないため 既に削除しています 新しい attached member macro を 宣言するために @attached(member) 属性を 使用します
それを SlopeSubset と呼びます
導入するメンバの名前も定義します
イニシャライザの 生成方法を紹介します 生成する計算プロパティも 非常に似ています すべてのケースを スイッチステートメントで 処理するだけです この宣言によりマクロが 定義されましたが 実際に実行される展開は まだ実装されていません
そのためマクロは WWDCMacrosモジュールの SlopeSubsetMacro型を 参照します 実際のマクロの 実装に進む前に SlopeSubsetMacro型を 作成しましょう 本当に興味深い部分です
SlopeSubsetを attached member macroと 宣言した為 対応する実装は MemberMacroプロトコル
このプロトコルには 1つの要件があります 'expansion' 関数です ExpressionMacro と 似ています
'expansion' 関数は マクロを宣言に 適用する為に 使用する属性と マクロが適用される 宣言を受け取ります これはEasySlopeの列挙型 の宣言になります
その後 マクロは宣言に追加する 全ての新しいメンバの リストを返します
実装に取り掛かる前に テストケースを作成する ことに同意しましたので そのように進めましょう
現時点では新メンバーを 追加する必要がないことを 示すために 空の配列を返します
そして SlopeSubsetを公開します providingMacros プロパティに SlopeSubsetを追加します
テストする前に 機能が正常に動作するか 確認します Xcodeでマクロを適用して 展開されたコードを 確認できますが テストケースを 作成しておくと良いです そうすればマクロに変更を 加える際に何度も実行して マクロの展開結果を 確認できます
テンプレートの テストケース同様に 'assertMacroExpansion'で 動作を検証します
テストケースの入力として EasySlope型にマクロを 適用した場合に 生成内容をテストする為 それを入力として使用します
マクロはまだ 何もしていないので 属性を削除し 新メンバーを追加しない ことを期待します そのため 期待される展開コードは 入力と同じであり @SlopeSubset を除いた ものです
最後にテストケースに SlopeSubset を展開する際に SlopeSubsetMacro の実装を 使用する必要があることを 伝える必要があります それにはマクロ名を その実装タイプにマッピングする 必要があります testMacros 辞書内に追加し アサーション関数に渡します
では今までに書いたコードが 正しく機能するか テストを実行しましょう
機能しました 良かったです ですが我々が望むのは マクロがイニシャライザを 生成するか確認する為で 単に属性を削除するだけでは ありません ではプラグインが生成することを 期待するコードを テストケースに 手動でコピーしましょう
テストを再実行すると
失敗します マクロがイニシャライザを まだ生成していないからです
修正してみましょう
イニシャライザはEasySlopes の列挙型の要素を変換します まずは enum の要素を 取得するために 宣言からそれらを取得する 必要があります enum要素はenum宣言の 内部でのみ宣言できる為 まずは declaration を enum宣言にキャストします
もしマクロがenumではない 型に付与された場合 エラーを出力する 必要があります 後で実装する事を忘れない様 TODOに追加し 現時点では空の配列を 返します 次に列挙型が宣言する 全ての要素を取得する必要がある 構文構造を調査するために SwiftSyntaxツリー内の 列挙型を検査します
マクロの実装は通常の Swiftプログラムと同じで Xcodeデバッグツールを使用して プログラムをデバッグ 例えば展開関数内に ブレークポイントを設定し テストケースを実行して デバッグできます
デバッガは現在マクロの実装の中で 一時停止しており 'enumDecl' は EasySlopesの列挙型です 'po enumDecl' と入力して 表示する事ができます
出力を調べましょう
構文Treeの内側のノードは enumの要素を表しています 'beginnersParadise' と 'practiceRun' それらを取得するには 構文Treeで示された 構造に従う必要があります その構造を徐々に確認して 進行しながら アクセスコードを 記述しましょう
列挙型宣言に memberBlock という子要素があります memberBlock には ブレースと 実際のメンバが 含まれています メンバにアクセスするには 'enumDecl.memberBlock. members' と始めます
こちらのメンバには 実際の宣言とオプションのセミコロンが 含まれています 私たちは特に列挙ケースの 宣言に興味があります enumケースであるメンバ宣言の リストを取得する為に compact map を使用します 各ケース宣言は複数の要素を 宣言できます これは別々の ケースキーワードの後に 各スロープを 新しい行で宣言する代わりに 'case beginnersParadise practiceRun' のように 同じ行に書くことも できるためです
全ての要素を取得するには 'flatMap' を使用できます
そして 全ての要素を取得したので 実際に EasySlope に 追加するイニシャライザを 構築することができます
イニシャライザの宣言には 1つの要素があります それは スイッチ式です
スイッチ式には 各要素に対応するケースと nil を返す デフォルトケースがあります そのため構文ノードを 作成する必要があります
構文ノードを作成するための 良い方法は 前述のように 構文Treeを出力するか SwiftSyntaxのドキュメントを 読むことです
まず InitializerDeclSyntax を 構築します
この型はResultビルダーを使用して 本文を構築し 'init' キーワードと全ての パラメータを指定する事で 構築できます これによりResultビルダー内で forループを使用して 全て要素を 繰り返し処理できます
テストケースから init の ヘッダをコピーしました
ボディ内部には スイッチ式が必要です
この型にはヘッダーと Resultビルダーを受け取る イニシャライザもあります もう一度やってみましょう
これで以前収集した 全ての要素を 反復処理することができます
各要素ごとに新しい ケースアイテムを作成し #stringify で見たのと 同様の文字列補間を 使用して構築したいですね
また nil を返すデフォルトの ケースも追加したいです
そして最後にイニシャライザを 返します
イニシャライザが生成されているか 確認しましょう
成功です マクロが正常に動作する事が 分かったので アプリで使用を開始できます
マクロパッケージをXcode プロジェクトに追加するには 右クリックでこれを選択 "Add Package Dependencies" これで作成したローカル パッケージを選択できます
マクロを使用できる様にする為に アプリとの依存関係として WWDCを追加します
パッケージから WWDCモジュールをインポートし EasySlope型にSlopeSubset マクロを適用できます
…
もしビルドすると... コンパイラは手動で 書かれたイニシャライザが 無効な再宣言であると 警告します 手動のイニシャライザの 再宣言が問題となります なので削除しましょう
コード削除すると スッキリするよね マクロが実際に生成したコードを 確認する為には SlopeSubset を右クリックし Expand Macro を選択する
もしマクロの動作を忘れたら Optionキーを押しながら マクロをクリックすれば解決
次は計算プロパティも 生成することですが それは後で行う予定です マクロを使用することで 重複のコードを書く必要なく EasySlopesの型の安全性を 得ることができました これはどうやったのでしょうか Swiftのマクロパッケージ テンプレートから始めました 構文Treeの構造を 調査するために マクロの実行を停止し デバッガ内で 構文ノードを出力しました これによりアクセスする 必要のあるプロパティを 確認できました
テストケースを使った マクロ開発は簡単でした アプリに追加した後 すぐに機能しました マクロがサポートしていない 状況で使用されたら? 予期しない展開やエラーの 発生を避けるため マクロがサポートしていない状況で 使用される場合の対処が必要です マクロがサポートしていない方法で 使用する場合 エラーメッセージを表示し 問題を理解し修正できるように しましょう
コードベースに残していた TODOを修正しましょう SlopeSubset が enum ではない型に適用されたら マクロは enum にのみ 適用可能であるという エラーを出力する必要が あります 以前と同様にテストケースを 追加しましょう
今回はSlopeSubsetマクロを 構造体に適用しています
構造体内には 列挙型の要素がないため マクロがイニシャライザ生成 しない事を予期しています 代わりに SlopeSubset が 列挙型に適用できる事を示す 診断(エラー)メッセージが 生成されるべきです テスト実行しても ただ失敗します エラーメッセージが 出力されていないからです コンパイラプラグインに 移動して出力しましょう
マクロのエラーは SwiftのErrorプロトコルに 準拠する任意の型で 表現することができます エラーメッセージ用の 単一のケースを持つ enumを使用します
エラーを expansion 関数から 投げると 呼び出し元の属性で エラーが表示されます
context パラメータには addDiagnostic があり それを使うと異なる場所で エラーメッセージ表示や 警告を生成したり Xcode で Fix-Its を 表示することができます しかし単純なエラーメッセージを 属性で表示するだけで効率的です さて 修正によって 成功するでしょうか
成功しました SlopeSubset を適用すると Xcodeでどう表示されるのか テストケースをファイルに コピーしてみましょう
Xcode はカスタム エラーメッセージを 他のコンパイルエラーと 同じ行内に表示します 私のマクロを利用する人が 何を間違えるか分かります
それに良いエラーハンドリングを 持つことで このマクロはスロープだけでなく 他のデベロッパが列挙型の サブセットを指定する際に 役立つかもしれませんね それでは汎用化しましょう
これまでハードコード されていた スーパーセットの列挙型 (Slope) を指定するために マクロ宣言にジェネリック パラメータを追加します
そしてマクロはスロープに 特化していないため EnumSubset という 名前に変更して下さい 右クリックで変更できます SlopeSubset から名前変更
文字列リテラルとコメント内の リネームするには コマンドをクリックします
ハードコードされた Slopesの代わりに ジェネリックパラメータを 使用して マクロの実装を調整する 必要があります デバッガーで属性を出力し そのレイアウトを調べると 'enumDecl' と同様に 属性の名前の genericArgumentClause の 最初の引数 'argumentType' にアクセスすることで ジェネリックパラメータを 取得できることが分かります ジェネリックパラメータを 取得したので これまでハードコードした Slope の代わりに 'supersetType' を 使用する事ができます
更にいくつかの変更が必要で イニシャライザの パラメータ名を変更し 型名をジェネリック パラメータに合わせて変更し それに伴いドキュメントも 更新する必要があります 後でやります テストが正常に動作していることを 確認しましょう
EnumSubset を ジェネリック化したため EasySlope が Slope の サブセットであることを 明示的に指定する 必要があります
テストを見てみましょう
大丈夫そうです Swiftパッケージとして 公開する事を検討します 本日は多くの内容を カバーしました 振り返りをしましょう マクロを作成時は マクロパッケージの stringify マクロが含まれた テンプレートを使用します マクロを開発する際 マクロが生成するコードが 正常か確認するために テストケースを作成する事を 強くお勧めします その際に expansion 関数で ブレークポイントを設定し テストを実行して構文Treeを デバッガで表示する事で 構文Treeのレイアウトを 調査できます 最後にマクロが特定の状況で 適用できなくても エラーメッセージを出力して マクロの使いやすさを 維持できます ご視聴ありがとうございます ♪♪
-
-
5:55 - Invocation of the stringify macro
import WWDC let a = 17 let b = 25 let (result, code) = #stringify(a + b) print("The value \(result) was produced by the code \"\(code)\"")
-
6:31 - Declaration of the stringify macro
@freestanding(expression) public macro stringify<T>(_ value: T) -> (T, String) = #externalMacro(module: "WWDCMacros", type: "StringifyMacro")
-
7:10 - Implementation of the stringify macro
public struct StringifyMacro: ExpressionMacro { public static func expansion( of node: some FreestandingMacroExpansionSyntax, in context: some MacroExpansionContext ) -> ExprSyntax { guard let argument = node.argumentList.first?.expression else { fatalError("compiler bug: the macro does not have any arguments") } return "(\(argument), \(literal: argument.description))" } }
-
9:12 - Tests for the stringify Macro
final class WWDCTests: XCTestCase { func testMacro() { assertMacroExpansion( """ #stringify(a + b) """, expandedSource: """ (a + b, "a + b") """, macros: testMacros ) } } let testMacros: [String: Macro.Type] = [ "stringify": StringifyMacro.self ]
-
12:05 - Slope and EasySlope
/// Slopes in my favorite ski resort. enum Slope { case beginnersParadise case practiceRun case livingRoom case olympicRun case blackBeauty } /// Slopes suitable for beginners. Subset of `Slopes`. enum EasySlope { case beginnersParadise case practiceRun init?(_ slope: Slope) { switch slope { case .beginnersParadise: self = .beginnersParadise case .practiceRun: self = .practiceRun default: return nil } } var slope: Slope { switch self { case .beginnersParadise: return .beginnersParadise case .practiceRun: return .practiceRun } } }
-
14:16 - Declare SlopeSubset
/// Defines a subset of the `Slope` enum /// /// Generates two members: /// - An initializer that converts a `Slope` to this type if the slope is /// declared in this subset, otherwise returns `nil` /// - A computed property `slope` to convert this type to a `Slope` /// /// - Important: All enum cases declared in this macro must also exist in the /// `Slope` enum. @attached(member, names: named(init)) public macro SlopeSubset() = #externalMacro(module: "WWDCMacros", type: "SlopeSubsetMacro")
-
15:24 - Write empty implementation for SlopeSubset
/// Implementation of the `SlopeSubset` macro. public struct SlopeSubsetMacro: MemberMacro { public static func expansion( of attribute: AttributeSyntax, providingMembersOf declaration: some DeclGroupSyntax, in context: some MacroExpansionContext ) throws -> [DeclSyntax] { return [] } }
-
16:23 - Register SlopeSubsetMacro in the compiler plugin
@main struct WWDCPlugin: CompilerPlugin { let providingMacros: [Macro.Type] = [ SlopeSubsetMacro.self ] }
-
18:41 - Test SlopeSubset
let testMacros: [String: Macro.Type] = [ "SlopeSubset" : SlopeSubsetMacro.self, ] final class WWDCTests: XCTestCase { func testSlopeSubset() { assertMacroExpansion( """ @SlopeSubset enum EasySlope { case beginnersParadise case practiceRun } """, expandedSource: """ enum EasySlope { case beginnersParadise case practiceRun init?(_ slope: Slope) { switch slope { case .beginnersParadise: self = .beginnersParadise case .practiceRun: self = .practiceRun default: return nil } } } """, macros: testMacros ) } }
-
19:25 - Cast declaration to an enum declaration
guard let enumDecl = declaration.as(EnumDeclSyntax.self) else { // TODO: Emit an error here return [] }
-
21:14 - Extract enum members
let members = enumDecl.memberBlock.members
-
21:32 - Load enum cases
let caseDecls = members.compactMap { $0.decl.as(EnumCaseDeclSyntax.self) }
-
21:58 - Retrieve enum elements
let elements = caseDecls.flatMap { $0.elements }
-
24:11 - Generate initializer
let initializer = try InitializerDeclSyntax("init?(_ slope: Slope)") { try SwitchExprSyntax("switch slope") { for element in elements { SwitchCaseSyntax( """ case .\(element.identifier): self = .\(element.identifier) """ ) } SwitchCaseSyntax("default: return nil") } }
-
24:19 - Return generated initializer
return [DeclSyntax(initializer)]
-
25:51 - Apply SlopeSubset to EasySlope
/// Slopes suitable for beginners. Subset of `Slopes`. @SlopeSubset enum EasySlope { case beginnersParadise case practiceRun var slope: Slope { switch self { case .beginnersParadise: return .beginnersParadise case .practiceRun: return .practiceRun } } }
-
28:00 - Test that we generate an error when applying SlopeSubset to a struct
func testSlopeSubsetOnStruct() throws { assertMacroExpansion( """ @SlopeSubset struct Skier { } """, expandedSource: """ struct Skier { } """, diagnostics: [ DiagnosticSpec(message: "@SlopeSubset can only be applied to an enum", line: 1, column: 1) ], macros: testMacros ) }
-
28:48 - Define error to emit when SlopeSubset is applied to a non-enum type
enum SlopeSubsetError: CustomStringConvertible, Error { case onlyApplicableToEnum var description: String { switch self { case .onlyApplicableToEnum: return "@SlopeSubset can only be applied to an enum" } } }
-
29:09 - Throw error if SlopeSubset is applied to a non-enum type
throw SlopeSubsetError.onlyApplicableToEnum
-
31:03 - Generalize SlopeSubset declaration to EnumSubset
@attached(member, names: named(init)) public macro EnumSubset<Superset>() = #externalMacro(module: "WWDCMacros", type: "SlopeSubsetMacro")
-
31:33 - Retrieve the generic parameter of EnumSubset
guard let supersetType = attribute .attributeName.as(SimpleTypeIdentifierSyntax.self)? .genericArgumentClause? .arguments.first? .argumentType else { // TODO: Handle error return [] }
-
-
特定のトピックをお探しの場合は、上にトピックを入力すると、関連するトピックにすばやく移動できます。
クエリの送信中にエラーが発生しました。インターネット接続を確認して、もう一度お試しください。