コンテンツにスキップ

ADR-0032: Add `maybe out` for Nullable Extern Output Parameters

Accepted

ADR-0026はsource-levelの不存在をcompiler専用型ではなく通常のgeneric enum Maybe<T>で表し、外部referenceのvalidity判定にはVRC.SDKBase.Utilities.IsValidを用いる方針を定めた。ADR-0030はこの方針を宣言的bindingのnormal returnへ適用するmaybe externを導入した。ADR-0031はCLR/Udonのref/out parameterをphysical ABI metadataとして保持し、通常returnとparameter宣言順のref/out outputをSobakasuのlogical returnへ投影する。

しかし、bool TryGet(out GameObject value)のように、normal returnとは独立してout valueがnullまたはinvalid referenceになり得るAPIを、logical return (bool, Maybe<GameObject>)として宣言する方法がない。raw outをすべて自動的にMaybe<T>へ変換すると、ADR-0026が維持するlow-level escape hatchを失い、physical overload selectionとlibrary policyも混同する。

Extern ABI parameterにmaybe outを追加する

Section titled “Extern ABI parameterにmaybe outを追加する”

ADR-0031の宣言的extern binding右辺に限り、次のparameter specificationを許可する。

fn try_get() -> (bool, Maybe<GameObject>)
= extern External.TryGet(
maybe out GameObject value,
)

maybeはこの位置だけで意味を持つcontextual keywordである。通常のfunction parameter、normal ABI parameter、refには導入しない。したがってmaybe ref、maybe T name、通常function parameterのmaybe outは不正とする。

Physical ABIとlogical output projectionを分離する

Section titled “Physical ABIとlogical output projectionを分離する”

out Tとmaybe out Tは、どちらもphysical parameter mode/typeとしてはout Tである。maybeはoverload resolutionへ参加せず、同じCLR/Udon overloadを選択できる。Binderはphysical overloadを確定した後にlogical projectionを適用し、metadataで次を区別する。

PhysicalPassingMode: Normal | Ref | Out | In
LogicalOutputProjection: Raw | Maybe

logical outputは次のとおりである。

out T -> T
maybe out T -> Maybe<T>

normal returnを先頭に置き、その後へparameter宣言順のref/out outputを並べるADR-0031の規則は変更しない。refとoutを種類別に並べ替えない。output数の正規化も維持する。

0 outputs -> ()
1 output -> T
2+ outputs -> (T0, T1, ...)

このためvoid Foo(maybe out GameObject value)のlogical returnはMaybe<GameObject>であり、(Maybe<GameObject>,)ではない。

既存のMaybe validity projectionを再利用する

Section titled “既存のMaybe validity projectionを再利用する”

maybe out Tは、既存のmaybe externと同じ規則でvalidity check可能なreference-like external valueに限る。value type等へ適用した場合はBinder diagnosticとする。新しいnullable type分類は導入しない。

loweringはphysical out T temporaryを確保してextern callを一度だけ実行し、そのtemporaryをUtilities.IsValidとMaybe payloadの双方で再利用する。validならsingle-payload variant、invalidならunit variantを構築する。variantはvisibleなgeneric enum Maybe<T>の形状から、validity methodはextern catalogと既存resolverからBinderが解決する。

Maybe<T>はADR-0026どおり通常のgeneric payload enumであり、既存aggregate flatteningを用いる。Maybe専用runtime object、opcode、UASM signature文字列、variant名のbackend hard-code、backend-only nullability inferenceは追加しない。normal returnがboolであっても、その値とout valueのvalidityを結合する規則は設けない。

  1. すべてのreference outを自動的にMaybe<T>へ変換する。raw ABI accessを失い、将来のgenerator policyまでcompiler semanticsへ混在させるため採用しない。
  2. out Maybe<T>としてphysical ABI型も変更する。実在するCLR/Udon overloadと一致しなくなるため採用しない。
  3. maybe refも同時に導入する。入力時と出力時のpresence semanticsを別途設計する必要があり、今回のnullable output projectionを越えるため採用しない。
  4. normal returnのboolをMaybe tagとして使用する。API固有の成功条件とUnity/VRChat reference validityを誤って結合するため採用しない。

physical mode/typeとlogical projectionを分離すれば、既存のreflection catalogとoverload resolverを唯一のphysical target選択経路として維持できる。BinderでMaybe enumとvalidity methodまで解決し、IR Lowererは解決済みprojectionを通常のcall、branch、aggregate constructionへ展開できるため、ADR-0003の責務分離も保たれる。

ADR-0026の通常のMaybe<T> policyとraw extern escape hatchを変更せず、ADR-0030のmaybe externを置き換えず、ADR-0031のref/out logical adaptationだけをparameter単位へ拡張する。

  • nullableなextern outをlogical Maybe<T>として明示できる。
  • outとmaybe outは同じphysical overloadを使用する。
  • extern callは一度だけ実行され、out temporaryをvalidity判定とpayloadで共有する。
  • 複数outputの宣言順とtuple flatteningを維持できる。
  • backendへsource syntax、Maybe解決、nullable policyを持ち込まない。
  • ABI parameter metadataとloweringにparameter単位のprojection情報が増える。
  • maybe outにはvisibleな適切なMaybe<T>とcatalog-backed Utilities.IsValidが必要になる。
  • generatorがどのAPIをmaybe outにするかというpolicyは別途設計が必要であり、当面のdefault生成はraw outのままとなる。
  • maybe ref、T?、一般的なnullable reference type system
  • ownership、borrow、lifetime、通常functionのref/out
  • reference outputの自動nullable inferenceまたはpolicy configuration
  • static classのtop-level function化、predicate命名変換、documentation URL生成