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 的其餘部分是可預測的型別時。

目前 FIDL 語法正進行重大遷移,這是 RFC-0050:語法改版工作的一部分。這也是實作屬性語法變更的好機會。

設計

語法

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

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

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

const DEFAULT_TRANSPORT string = "Channel";

@transport(DEFAULT_TRANSPORT)
protocol P {};

引數必須以 lower_snake_case 的「關鍵字」語法表示,類似於 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/interface 上新增 #/definitions/compose 定義做為屬性,並正式將 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 範例,type S 的 JSON IR 輸出內容中,maybe_attributes 欄位會是:

"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 是介面定義語言,因此何不也使用 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" 簡單轉換為 int 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 的註解結構定義可做為參考點,用來取代本文提出的語法,提供功能更豐富的替代方案。