sidepelican / CallableKit

Typesafe rpc with Swift server
1 stars 1 forks source link

C2TSのPackageGeneratorを利用する形に書き換える提案 #25

Closed omochi closed 1 year ago

omochi commented 1 year ago

C2TSにPackageGeneratorという機能を追加しました。 従来のCodeGeneratorは1つのSwiftの型を受け取って、1つのTypeScriptソースを返すAPIでした。 また、そのソース生成に含まれる様々な単位でのコード生成も提供していました。 ユーザーがC2TSを利用するには、CodeGeneratorを活用してうまいことTypeScriptプロジェクトを構成する必要があり、(暗黙的な想定の用法はあれども)工夫が必要でした。 また、import文の自動生成のためにはそれなりのボイラープレートコードが必要でした。

PackageGeneratorは、複数のSwiftのモジュールを受け取って、 複数のTypeScriptソースからなるライブラリディレクトリを生成するAPIです。 これにより、C2TSが想定する標準的なプロジェクト生成が定義され、利用しやすくなりました。 内部ではCodeGeneratorを使っていて、それを叩けば従来の細かいAPIも使えます。 もちろんimport文の自動生成も面倒を見ます。


そこで、CallableKitもこれを利用する形に書き換える事を提案します。 理由は以下の2つです。

  1. C2TSの標準的な方法を利用することにより、C2TSだけ利用しているユーザープロジェクトが追加でCallableKitを導入したり、CallableKitを導入しているプロジェクトがそれを廃止しつつもC2TSの利用は残すなど、構成の変更においてユーザープロジェクトが壊れにくくなります。

  2. 将来、CallableKitのように、C2TSを利用する異なるコード生成ツールBが出現するかもしれません。そしてもしユーザーがツールBとCallableKitの両方をプロジェクトに組み込みたいとなった場合に、それぞれがC2TSの利用を内臓していると、コード生成動作が重複したりするかもしれず、統合するのが設計上難しくなる恐れがあります。 CallableKitの構成を、C2TS標準部分と、CallableKitによる拡張部分として整理する事で、こういった状況でも設計を統合しやすくなる事が期待できます。


これを採用する場合に生じる具体的な変化を説明します。 PackageGeneratorには大まかには、Swiftのモジュールと出力ディレクトリを指定すると、そのディレクトリの中にソースを生成します。 共通ライブラリのcommon.tsと、型ごとに分離されたソースが生成されます。 以下は生成先としてsrc/gen/typesディレクトリを指定した場合の例です。

src/gen/types/
├ common.ts
├ User.ts
└ Student.ts

TypeMapなどによりユーザー定義関数を生成物に組み込む場合は、PackageGeneratorにTypeScriptAST.SymbolTableを渡すインターフェースがあるので、そうした外部のシンボルを登録したテーブルを渡せば、適切なimport文を含むコードが生成されます。

例えば既存の例だと Date_decode 関数の実装などがあります。 これらはいわばCallableKitが定義する標準ライブラリなので、src/gen/CallableKit.tsに定義するとします。 また、CallableKitはサービスプロトコルの定義と、その実装を生成するので、これらは従来どおりsrc/gen/<Service>.gen.ts に生成するとします。 すると、以下のようなソースツリーが生成されます。

src/
└ gen/
  ├ types/
  │ ├ common.ts
  │ ├ User.ts
  │ └ Student.ts
  ├ CallableKit.ts
  ├ Echo.gen.ts
  └ Account.gen.ts

従来、EchoHelloRequest型なども Echo.gen.ts に含まれていましたが、 これはC2TSの役割であるSwiftの型のTypeScriptへのトランスパイルする挙動に含まれるため、 Echo.gen.ts には含まれなくなります。 つまり、ソースツリーは以下のようになります。

src/
└ gen/
  ├ types/
  │ ├ common.ts
  │ ├ User.ts
  │ ├ Student.ts
  │ ├ EchoHelloRequest.ts
  │ └ EchoHelloResponse.ts
  ├ CallableKit.ts
  ├ Echo.gen.ts
  └ Account.gen.ts

以前会話したときに、Echo.gen.ts にリクエスト型が含まれているのは元のSwiftソースが持っている一覧製が再現できるメリットがある、という事を言っていたと思います。これについてはメリットが失われてしまう問題があります。 ただ個人的には、vscodeであればEchoClientのメソッド部分に書かれた引数の型からEchoHelloRequest型の定義にはジャンプできるので、大きな不便は無いと思います。

また、 IEchoClient の生成に関しては、 将来C2TS側でプロトコル変換を提供したいと思っているので、 その場合には Echo.gen.ts からこれも移動し、 また、 I prefixは付けないつもりなので以下のようになります。

src/
└ gen/
  ├ types/
  │ ├ common.ts
  │ ├ User.ts
  │ ├ Student.ts
  │ ├ EchoClient.ts
  │ ├ EchoHelloRequest.ts
  │ └ EchoHelloResponse.ts
  ├ CallableKit.ts
  ├ Echo.gen.ts
  └ Account.gen.ts

そして、Echo.gen.tsに含まれるのは export const bindEcho だけになるでしょう。


実装作業については、提案に賛同してもらえるのであれば、こちらでやってPRを出します。 または、結論は先送りでいったん検討するために実装してみるのもOKです。

omochi commented 1 year ago

書いてから思いましたが、C2TSもCallableKitのようにソースファイルを1:1対応で変換するようにして、 CallableKitなどが使う追加の生成処理を登録するAPIを設けるのが良いかもしれません ソースで対応付ける方がユーザーにとって直感的だし、CallableKit側でファイルツリーを見渡す処理が外せそう

omochi commented 1 year ago

C2TSのコード生成は元のSwiftソースツリーをそのまま対応付けて生成する事にしました。 よってCallableKitのexampleの場合、生成結果は以下のようになります。

src/
└ gen/
  ├ common.ts
  ├ APIDefinitions/
  │ ├ Account.ts
  │ ├ Echo.ts
  │ └ Entity/
  │   ├ GenericID.ts
  │   ├ Student.ts
  │   ├ SubmitError.ts
  │   └ User.ts
  └ OtherDependency/
    └ CodableResult.ts

C2TSは struct, enum, typealias しか面倒を見ませんが、 var didGenerateEntry: ((SourceFile, PackageEntry) -> Void)? により各種ソースの生成後に割り込んで追加の加工処理ができるので、 Echo.ts の中に CallableKit による従来の IEchoClientbindEcho の生成を差し込む事ができます。

sidepelican commented 1 year ago

モジュール構成の仕組みをC2TS側に組み込むのはいいアイデアだと思いました。 利点に挙げられてるようにユーザにとってわかりやすいし、依存性の観点でも良いと思います。

didGenerateEntry で加工する方針に関しても良さそうに思います。 インターフェースはちょっと分かりにくいとは思いました。(PackageEntry.source を直接加工する? ) 気持ち的には追加で足したいものを吐き出す気持ちを表すために (...) -> [any ASTNode] の形式がいいかと思いましたが、これだとPackageEntryの中身を直接いじる手法も用意されていて編集方法が複数存在する点が微妙かもしれませんね。また余分なdeclを削除するみたいなオペレーションを返り値に表すことも難しいですね。 この辺は実際にCallableKit側の実装をこれに書き換えてみてどう思ったかで調整すれば良いと思います。

omochi commented 1 year ago

ありがとうございます。 では試しに組み込んでみます。

PackageRntry.sourceを直接加工する事を想定しています。 挙げてもらったような追加の文を返す方法だと、末尾に文を追加する事しかできず、 新しい文を挿入する位置を選択したり、既存の文を書き換えたりする事ができないからです。

omochi commented 1 year ago

マージされた