ADR-0030: Declarative Extern Bindings
Status
Section titled “Status”Accepted
Context
Section titled “Context”SobakasuのStandardLibraryは、.NET、Unity、VRChat、UdonのAPIをSobakasuらしい名前と型で公開する。従来、単純な一対一のwrapperにも通常のblock bodyが必要だった。
pub impl Random = extern System.Random { pub fn next_double() -> f64 { extern self.NextDouble() }}この形式は実行上は十分だが、大量のwrapperを生成・保守するには冗長であり、Sobakasu symbolとexternal memberの対応を得るために任意のfunction bodyを再解析する必要がある。将来のreference/documentation生成では、選択済みoverload、引数型、戻り値型、static/instance、member kindを意味解析結果から直接取得できることが望ましい。
ADR-0008とADR-0016は、installed SDKとUdon node catalogに基づくextern候補収集、overload resolution、Udon extern signatureの確定をBinderの責務としている。ADR-0029も、未解決の候補をIRやbackendへ渡さず、Binderで具体的なcall targetを一意にする。新構文はこの共通resolverを再利用し、別のoverload規則を持ってはならない。
ADR-0026はsource-levelの不存在を通常のgeneric enum Maybe<T>で表し、reference-returning StandardLibrary wrapperではVRC.SDKBase.Utilities.IsValidを用いる方針を定めた。同時に、raw externを低レベルescape hatchとして維持し、自動的なMaybe<T>化を禁止している。
ADR-0003の責務分離、ADR-0011のfunction declaration、ADR-0015のzero-argument parentheses省略、ADR-0017のStandardLibrary boundaryも維持する必要がある。
Decision
Section titled “Decision”専用の宣言構文を追加する
Section titled “専用の宣言構文を追加する”function declarationのbodyとして、次の二形式だけを追加する。
fn NAME [parameters] [-> return_type] = extern EXTERNAL_ACCESSfn NAME [parameters] [-> return_type] = maybe extern EXTERNAL_ACCESS例えば次を許可する。
pub fn sqrt(value: f64) -> f64 = extern System.Math.Sqrt(value)
pub impl GameObject = extern UnityEngine.GameObject { pub static fn find(name: string) = maybe extern UnityEngine.GameObject.Find(name)
pub fn set_active(active: bool) = extern self.SetActive(active)}top-level function、impl内のinstance methodとstatic fnに同じ規則を適用する。引数がない場合の()省略はADR-0015の既存規則をそのまま使う。
maybeはこの構文の=直後だけで意味を持つcontextual keywordとして解析する。これは既存のmaybe module名とuse maybe.Maybeを壊さずに新構文を導入するためである。
一般的なfn ... = expressionは導入しない。=の後に許可するのはexternまたはmaybe externだけであり、既存のblock bodyも引き続き有効とする。
宣言をdirect semantic bindingとして保持する
Section titled “宣言をdirect semantic bindingとして保持する”Parserは通常のexpression bodyではなく専用のextern binding syntax nodeを生成する。Binderは既存のextern expression resolverを一度だけ呼び、選択された具体的なExternMethodSymbolをfunction symbolへ関連付ける。
意味解析結果は少なくとも次を保持する。
SobakasuSymbolExternalDeclaringTypeExternalMemberNameResolvedExternalSignatureInvocationKind: Static | InstanceMemberKind: Method | Getter | Setter | Constructor | OperatorReturnBindingMode: Raw | Maybeさらに、既存resolverが確定したUdon extern名、external parameter types、external return typeを同じresolved symbolから利用できる。名前だけのmember groupやsource textをmetadataとして保存しない。overloadされたAPIでは、Binderが引数型から選んだ一つの具体的なsignatureを保存する。
compilerの公開結果には、documentation generator等がParserを再実行せず利用できるread-onlyなexternal binding metadataを含める。URL生成規則やdocumentation site自体はこのADRでは定めない。
戻り値型を解決済みexternから決める
Section titled “戻り値型を解決済みexternから決める”戻り値型を省略したraw bindingでは、選択済みextern signatureの戻り値を既存のexternal-to-Sobakasu type mappingで変換し、functionの戻り値型として推論する。
明示した場合は、解決済みextern戻り値との既存の代入互換性規則を検証する。不一致は宣言位置のdiagnosticとする。voidを返すexternには、戻り値を省略した従来どおりの() functionを構成できる。
functionのsignatureはcall siteをbindする前に確定する。extern解決失敗、overloadのno-match/ambiguity、unsupported targetは既存のextern diagnosticsを再利用し、宣言的binding固有の形状またはreturn policyの失敗だけに専用diagnosticを用いる。
maybe externは通常のMaybe<T>を構築する
Section titled “maybe externは通常のMaybe<T>を構築する”maybe externのexternal resultをTとすると、functionの戻り値型はMaybe<T>になる。明示する場合はMaybe<T>と整合しなければならない。
TはVRC.SDKBase.Utilities.IsValidで検査可能なnon-void reference resultでなければならない。Binderはvisibleな通常のgeneric enum Maybe<T>とそのunit variant/single-payload variantを解決し、installed catalogからUtilities.IsValidの具体的なextern signatureを選択する。variant名、Udon signature文字列、backend専用Maybe表現はハードコードしない。
loweringは概念的に次を生成する。
let value = extern External.Find(...);if extern VRC.SDKBase.Utilities.IsValid(value) { Maybe.Just(value)} else { Maybe.Nothing}external callは必ず一度だけ評価し、その一時値をvalidity checkとpayloadの双方に使う。結果はADR-0021/ADR-0022の通常のflattened generic enumとしてIRへlowerする。UASM backendは解決済みextern call、branch、aggregate leafをemitするだけで、extern resolutionやMaybe判定を行わない。
通常の= externはraw resultを返し、reference resultを自動的にMaybe<T>へ変換しない。これによりADR-0026のraw extern escape hatchを維持する。
従来のwrapperを維持する
Section titled “従来のwrapperを維持する”次のblock-body wrapperは引き続き有効であり、宣言的bindingへ強制移行しない。
pub fn foo(value: i32) -> i32 { extern System.Math.Abs(value)}同じexternal memberと型を使用するraw declarative bindingは、temporary名やinternal labelを除いて、同じ解決済みUdon extern signatureと意味的に同等のcodeを生成する。
Alternatives
Section titled “Alternatives”現状のblock bodyだけを維持する
Section titled “現状のblock bodyだけを維持する”単純なbindingにもboilerplateが残り、direct bindingか任意のwrapper logicかをsyntaxから区別できない。documentation toolingがbody構造を再解析しなければならないため採用しない。
一般的なexpression-bodied functionを追加する
Section titled “一般的なexpression-bodied functionを追加する”fn foo = expressionは独立した言語機能として検討すべきであり、extern symbolとの直接対応を保証しない。今回必要なsemantic metadataを曖昧にするため採用しない。
optional externを使う
Section titled “optional externを使う”Sobakasuの既存のpresence型はMaybe<T>であり、新しいoptional用語を導入すると型名と構文が乖離するため採用しない。
option externを使う
Section titled “option externを使う”同様に、既存型名Maybe<T>と一致せず、二つの語彙を作るため採用しない。
extern?を使う
Section titled “extern?を使う”?はmethod名末尾に使用可能であり、punctuationの意味が衝突しやすい。raw/Maybeの意図も検索しにくいため採用しない。
try externを使う
Section titled “try externを使う”exception処理またはResult型を連想させるが、この構文が表すのはreference validityによるpresenceだけであるため採用しない。
safe externを使う
Section titled “safe externを使う”Maybe<T>は将来のlifetimeやあらゆる外部動作の安全性を保証せず、何をsafeとするか曖昧になるため採用しない。
Rationale
Section titled “Rationale”- StandardLibraryの大量生成と保守を容易にする。
- 単純なextern wrapperのboilerplateを削減する。
- Sobakasu APIとexternal APIの対応をcompilerが明示的なsemantic dataとして保持できる。
- 将来の.NET/Unity/VRChat公式documentationへの自動リンク生成を可能にする。
- 選択済みoverloadを再利用し、documentation側でCLR/Udon APIを再解決する必要をなくす。
Maybe<T>が必要なextern boundaryを構文上で明示する。- raw externとMaybe化されたexternをlibrary authorが明示的に選択できる。
- static APIとinstance APIを同じbinding modelで扱う。
専用syntax、Binder上のdirect metadata、解決済みbound callを組み合わせることで、sourceの簡潔さとtooling向けの機械可読性を同時に得られる。既存resolverを唯一のoverload選択経路とし、Maybeを通常のenumへlowerするため、Parser、Binder、IR、UASM backendの責務も維持される。
Consequences
Section titled “Consequences”Positive
Section titled “Positive”- 単純なStandardLibrary wrapperを一つの宣言として読める。
- return typeを解決済みexternal signatureから安全に推論できる。
- concrete overloadとUdon extern signatureを後続toolingが直接参照できる。
- top-level static APIと
impl内のstatic/instance APIで同じmodelを使える。 maybe externはexternal callを一度だけ評価し、既存のMaybe<T>layoutを再利用する。- block-body wrapperとraw externの互換性を維持する。
- backendへ型解決やreflectionを追加しない。
Negative
Section titled “Negative”- function signature collection中にextern targetとreturn typeを解決する経路が増える。
maybeをcontextual keywordとしてtoolingが認識する必要がある。maybe externはinstalled catalogに適切なUtilities.IsValidoverloadがなければ宣言できない。- public compiler resultに新しいmetadata surfaceが増え、将来のtoolingとの互換性を考慮する必要がある。
Non-goals
Section titled “Non-goals”- 一般的なexpression-bodied function
- external documentation URLのハードコード
- 完全なdocumentation site generator
- unrelatedなextern catalogの大規模拡張
Maybe<T>またはADR-0026の再設計- static field/property向けの新しいtop-level declaration syntax