RFC-0086:RFC-0050 更新:FIDL 屬性語法

RFC-0086:更新 RFC-0050:FIDL 屬性語法
狀態已接受
區域
  • FIDL
說明

指定 FIDL 語言中的新屬性語法。

問題
Gerrit 變更
作者
審查人員
提交日期 (年-月-日)2021-03-09
審查日期 (年-月-日)2021-04-07

摘要

本文將說明 Fidl 語言中屬性的新語法。

另請參閱:

提振精神

FIDL 屬性提供明確的方式,可將編譯時間中繼資料附加至 FIDL 宣告,讓作者將相關說明文件 (DocNoDoc)、編譯時間驗證 (ForDeprecatedCBindings)、程式碼產生 (TransitionalTransport)、所需 API 可用性 (Discoverable) 等額外資訊傳遞至 FIDL 編譯器。除了這些「官方」屬性之外,FIDL 作者也可以定義自己的「自訂」屬性,這些屬性不會影響編譯,但仍會附加至產生的 FIDL JSON 中繼表示法,以供後續使用。

現有屬性語法缺少以下三個屬性:

  • 多個屬性會以逗號分隔的單一宣告形式寫入,如下所示:[Discoverable, NoDoc, Transport = "Channel"]。這麼做不夠靈活,而且不清楚屬性是否彼此獨立。
  • 屬性目前只能接受零或一個引數,不得超過一個。
  • 當屬性確實接受引數時,值一律為字串。例如,MaxBytes 屬性會將字串化數字做為引數,如下所示:[MaxBytes="128"]。這會造成混淆,尤其是當 FIDL 的其餘部分是可預測的型別時。

RFC-0050:語法重整 計畫正在進行中,這項計畫是 FIDL 語法目前正在進行的重大遷移作業。這也是實作屬性語法變更的好時機。

設計

語法

每個屬性都是單一宣告,屬性名稱 (依慣例使用 lower_snake_cased) 會直接在 @ 符號前方顯示。對於含有多個屬性的宣告,屬性之間並沒有優先順序。舉例來說,屬性宣告 [Discoverable, NoDoc, Transport = "Channel"] 現在可以寫成:

@discoverable
@no_doc
@transport("Channel")
protocol P {};

視需要,屬性名稱後面可加上一組括號,其中包含以半形逗號分隔的一或多個引數清單。引數可以是任何有效的 FIDL 常數。例如:

const DEFAULT_TRANSPORT string = "Channel";

@transport(DEFAULT_TRANSPORT)
protocol P {};

引數必須使用 lower_snake_cased 的「關鍵字」語法表示,類似於 Python 中的關鍵字,但只有只接受一個引數的屬性必須省略關鍵字。引數類型和必要引數的存在,只會針對 fidlc 原生屬性進行驗證。對於這類屬性,編譯器可能會將省略的選用引數預設為某些預先決定的值。請參考以下模擬 @native 屬性的用法範例,該屬性會使用兩個必要引數 (req_areq_b) 和兩個選用引數(opt_aopt_b):

const C bool = true;
@native(req_a="Foo",req_b=3)                  // OK: neither opt arg is set
@native(req_a="Foo",req_b=3,opt_c=C)          // OK: only opt_a is set
@native(req_a="Foo",req_b=3,opt_d=-4)         // OK: only opt_b is set
@native(req_a="Foo",req_b=3,opt_c=C,opt_d=-4) // OK: both opt args are set
@native(req_a="Foo",req_b=3,opt_d=-4,opt_c=C) // OK: arg order is irrelevant
@native(opt_d=-4,req_a="Foo",req_b=3)         // OK: arg order is irrelevant
@native(req_b=3)                              // Error: missing req_a
@native(req_a="Foo")                          // Error: missing req_b
type S = struct {};

作者定義的自訂屬性沒有結構定義,且僅由 FIDL 檔案中的屬性描述。編譯器無法驗證引數的數量、是否為必要引數,以及引數的正確類型為何 (不過,如果我們選擇實作「In-FIDL」屬性結構定義,這可能會有所變更,請參閱下方「替代方案」一節)。因此,系統只會強制執行屬性簽章的語法正確性。以下是作者定義的屬性 @custom 的範例:

@custom(a="Bar",b=true) // OK: grammatically correct
@custom("Bar",true)     // Error: bad grammar - multiple args require keywords
@custom("Bar")          // OK: correct grammar (though signature now unclear)
@custom(true)           // OK: correct grammar (signature even more unclear)
@custom()               // Error: bad grammar - cannot have empty arguments list
@custom                 // OK: grammatically correct
type S = struct {};

正式來說,新 FIDL 屬性語法的經修改的 BNF 文法為:

attribute = "@", IDENTIFIER , ( "(" , constant | attribute-args, ")" ) ;
attribute-args = attribute-arg | attribute-arg, "," attribute-args;
attribute-arg = IDENTIFIER , "=" , constant;

RFC-0040:ID 唯一性適用於屬性名稱。也就是說,在標準名稱解析期間,屬性名稱中的不同大小寫和連續的底線會縮減為單一通用名稱。因此,以下範例會導致範圍衝突,並在編譯時產生錯誤:

@foo_bar
@FooBar   // Error: re-used attribute name "foo_bar"
@fooBar   // Error: re-used attribute name "foo_bar"
@Foo_Bar  // Error: re-used attribute name "foo_bar"
@foo__bar // Error: re-used attribute name "foo_bar"
@FOOBar   // Error: re-used attribute name "foo_bar"
type S = struct {};

JSON IR

屬性的 FIDL JSON 中繼表示法結構定義反映了新語法。結構定義的屬性定義現在包含 location 欄位,可追蹤屬性宣告的檔案內位置。value 欄位已由 arguments 欄位取代,後者會將每個引數的值儲存為 name/value 組合,其中 value 會採用 #/definitions/constant 的結構定義。如果屬性只接受一個引數,因此來源中沒有關鍵字 name,則唯一引數的名稱預設為 "value"

此外,compose 節和 reserved 表格/聯合成員先前無法攜帶屬性。這份 RFC 會修正這些疏失,新增 #/definitions/compose 定義做為 #/definitions/interface 的屬性,並正式將 maybe_attributes 屬性附加至 #/definitions/table-member

總而言之,這份 RFC 會引入三個新的結構定義 (#/definitions/attribute-arg#/definitions/attribute-args#/definitions/compose),並修改其他三個結構定義 (#/definitions/attribute#/definitions/interface#/definitions/table-member)。新的屬性定義如下所示:

"attribute-arg": {
  {
    "description": "Definition of an attribute argument",
    "type": "object",
    "required": [
        "name",
        "value",
    ],
    "properties": {
      "name": {
        "description": "Name of the attribute argument",
        "type": "string",
      },
      "value": {
        "description": "Value of the attribute argument",
        "$ref": "#/definitions/constant",
      },
      "location": {
        "description": "Source location of the attribute argument",
        "$ref": "#/definitions/location"
      },
    },
  },
},
"attribute-args": {
  {
    "description": "Definition of an attributes argument list",
    "type": "array",
    "items": {
      "description": "List of arguments",
      "$ref": "#/definitions/attribute-arg",
    },
  },
},
"attribute": {
  {
    "description": "Definition of an attribute",
    "type": "object",
    "required": [
        "name",
        "arguments",
        "location",
    ],
    "properties": {
      "name": {
        "description": "Attribute name",
        "type": "string",
      },
      "arguments": {
        "description": "Attribute arguments",
        "$ref": "#/definitions/attribute-args",
      },
      "location": {
        "description": "Source location of the declaration",
        "$ref": "#/definitions/location",
      },
    },
  },
},
"compose": {
  {
    "description": "Compose member of an interface declaration",
    "type": "object",
    "required": [
        "name",
        "location",
    ],
    "properties": {
      "name": {
        "$ref": "#/definitions/compound-identifier",
        "description": "Name of the composed interface"
      },
      "maybe_attributes": {
        "description": "Optional list of attributes of the compose declaration",
        "$ref": "#/definitions/attributes-list",
      },
      "location": {
        "description": "Source location of the compose declaration",
        "$ref": "#/definitions/location",
      },
    },
  },
},

接著,我們將繼續使用上一節的 @native 範例,其 JSON IR 輸出內容的 maybe_attributes 欄位會是:type S

"maybe_attributes": [
  {
    "name": "native",
    "arguments": [
      // Note: the omitted opt_d is not included in the IR
      {
        "name": "req_a",
        "value": {
          "kind": "literal",
          "value": "Foo",
          "expression": "\"Foo\"",
          "value": {
            "kind": "string",
            "value": "Foo",
            "expression": "\"Foo\"",
          },
        },
      },
      {
        "name": "req_b",
        "value": {
          "kind": "literal",
          "value": "3",
          "expression": "3",
          "literal": {
            "kind": "numeric",
            "value": "3",
            "expression": "3",
          },
        },
      },
      {
        "name": "opt_c",
        "value": {
          "kind": "identifier",
          "value": "true",
          "expression": "C",
          "identifier": "example/C",
        },
      },
    ],
    "location": {
        "filename": "/path/to/src/fidl/file/example.fidl",
        "line": 4,
        "column": 0,
        "length": 36,
    },
  },
],

個案研究:FIDL 版本管理

RFC-0083:FIDL 版本管理說明瞭以屬性為基礎的語法,可將版本中繼資料附加至 FIDL 宣告和成員。具體來說,它會定義新的屬性 @available,該屬性會採用可選的引數 platformsinceremoveddeprecatednote。例如:

@available(since=2)
type User = table {
  // Was created with struct at version 2, so no @available attribute is needed.
  1: is_admin bool;
  // Deprecated in favor of, and eventually replaced by, the name field.
  @available(deprecated=3,removed=4,note="use UTF-8 `name` instead")
  2: uid vector<uint8>;
  // The current (unreleased) version constrains this definition.
  // Placing "removed" before "since" is discouraged, but won't fail compilation.
  @available(removed="HEAD",since=3)
  3: name string;
  @available(since="HEAD")
  3: name string:60;
};

值得一提的是,參照編號平台版本的引數 (即 sinceremoveddeprecated) 也可能會採用特殊字串 "HEAD"。也就是說,這些引數沒有單一容易解析的類型,例如 uint8。由於「官方」屬性 (例如 @available) 的特定類型驗證規則已硬式編碼至編譯器本身,因此允許這類結構。其他較為細微的規則 (例如只有附加至 library 宣告的 @available 屬性才能攜帶 platform 引數) 也以這種方式處理。

實作

這項提案將納入更廣泛的 RFC-0050 FIDL 語法轉換作業。所有以「新」語法編寫的 FIDL 檔案都應符合本 RFC 中列出的變更,而正式的 FIDL 文法也會在 RFC-0050 的其他部分更新,以反映其設計。

此外,結構定義也會更新,以便配合本文件指定的 JSON IR 變更。不過,在實際遷移至 RFC-0050 定義的語法時,請務必讓 JSON IR 結構定義保持靜態,因為這能確保遷移前後的語法產生相同的 IR,是驗證新編譯器準確性的重要工具。因此,必須先更新本文件所提議的屬性定義,再進行任何 RFC-0050 遷移作業,以確保 JSON IR 在該程序進行期間不會變更。

成效

這些語法變更不太可能對效能造成影響。

安全性考量

這些語法變更不太可能對安全性造成重大影響。不過,這類屬性也有小小的好處,可讓日後可能的安全性驗證屬性更容易編寫和推理。

隱私權注意事項

這些語法變更不太可能對隱私權造成重大影響。不過,這類屬性確實有點小小的好處,可讓日後可能會驗證隱私權的屬性更容易編寫和推理。

測試

這些語法變更不太可能對測試造成重大影響。不過,這類屬性確實有點小小的好處,可讓日後的測試檢測屬性更容易撰寫和推理。

說明文件

所有相關說明文件和範例都會更新為新語法,以便納入更廣泛的 RFC-0050 說明文件更新。具體來說,官方 FIDL 屬性的參考文件將會更新,以反映此設計規定的規則,並註明每個屬性的有效引數類型和隱含預設值。

缺點、替代方案和未知事項

FIDL 內屬性別名結構定義

可用的語法幾乎無限,本節不會嘗試處理所有語法。不過,我們認真考慮的其中一個選項是允許使用 FIDL 定義註解函式的介面。以下說明這個替代語法,以及拒絕採用的原因。

請參考下列 FIDL 檔案,其中內嵌定義了自訂屬性的介面:

type MyAttrArgs = table {
  1: foo string;
  2: bar int32;
  3: baz bool;
};
@myAttr(MyAttrArgs{foo: "abc", bar: 1, baz: false})
type MyStruct = struct {...};

這種設計的好處是「自食自足」:FIDL 是介面定義語言,因此為何不使用它定義內建的編譯器感知屬性函式?此外,這可讓自訂的使用者定義屬性在日後的某個時間點啟用,但編譯器如何將此類使用者定義的元資料附加至產生的繫結程式碼,仍是個未知數。

這個設計路徑最終遭到拒絕,因為它試圖執行太多功能。在可預見的未來,沒有任何屬性用途需要這種語言內定義功能,甚至還不確定啟用使用者定義屬性是否是理想的未來目標。本文件所提的語法更簡單,也更符合 Rust 和 Python 等其他熱門語言的使用者。

目前的設計中並未明確禁止實作 In-FIDL 屬性結構定義,因此這個替代方案仍是日後擴充屬性語法的可行選項。

使用字串常值做為屬性引數

針對本文件中指定的設計,可能會做出的一種變更是禁止使用指定類型的引數,並保留舊版模式,要求所有引數值皆為字串文字。請參考以下範例,瞭解這類設計:

const MAX_STR string = "123";
const MAX_NUM uint64 = 123;

@max_handles(123)     // Error: cannot use non-string types
@max_handles(MAX_STR) // Error: cannot use const identifiers
@max_handles(MAX_NUM) // Error: cannot use non-string types or const identifiers
@max_handles("123")   // OK: only string literals are allowed
type S = struct {};

這項設計的優點在於實作方式簡單。為了支援具有類型的屬性引數,後端需要相對複雜的擷取邏輯,才能正確處理每個屬性引數的各種可能類型。先前只要將字串 "123" 簡單轉換為整數 123 即可,但現在後端需要處理整個 #/definitions/constant 結構定義。這項額外的實作成本會乘以所支援的後端數量。

允許類型屬性的好處在於,這可將這類轉換邏輯集中管理。舉例來說,請考慮屬性宣告 @my_custom_attr("3.")。如果每個後端都會執行自己的型別轉換,其中一個可能會判定 "3." 是可轉換為整數的有效值,而另一個可能不會。這類問題的細節很難一一掌握,因此後端在實作屬性時,難免會出現差異。將屬性類型在 fidlc 中的行為方式的標準化理解納入考量,即可消除這類不一致性。

遭拒的次要變更

本文件所述的屬性語法明確指出,附加至單一宣告的多個屬性之間的順序無關緊要。另一種做法是強制採用字母順序。這項提案遭到拒絕,因為作者定義的自訂屬性和未來的 fidlc 原生屬性,可能會以某種方式彼此參照,因此可透過使用特定順序來提升清晰度。請考慮下列兩個作者定義的自訂屬性,如果需要依字母順序排序,則其順序會變得難以理解:

@this_attr("Foo")
@test_for_this_attr(false)
protocol P {};

此外,camelCase 也被視為屬性語法建議的大小寫慣例。這項建議最終遭到拒絕,因為 FIDL 樣式指南中沒有其他建議使用 camelCase,且我們認為只為屬性新增新的大小寫風格會造成過度混淆。

既有技術與參考資料

這份 RFC 是 RFC-0050:語法改良 中定義的語法演進版本,因此會修改正式的 FIDL 文法

這項提案參考了其他語言中許多現有的「類似屬性」實作方式,具體如下:

  • Python 的修飾符語法關鍵字引數設計,是本文件中部分設計選項的靈感來源,例如使用 @ 符號和以關鍵字參照引數。
  • 雖然 Rust 的屬性規格在某些語法選擇上表面上有所不同,但在概念上也與這項提案相似。
  • 我們將 Cap'n Proto 的註解結構定義視為參考點,以便提供功能更豐富的替代方案,取代本文件中提出的語法。