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

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

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

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

摘要

本文件說明 FIDL 語言中屬性的新語法。

另請參閱:

提振精神

FIDL 屬性提供明確的方式,可將編譯時間中繼資料附加到 FIDL 宣告中,允許作者針對與說明文件 (DocNoDoc) 編譯時間驗證 (ForDeprecatedCBindings)、程式碼產生 (轉換可偵測)、所需的 API 可用性 () 相關的 FIDL 編譯器傳遞更多資訊除了這些「官方」屬性外,FIDL 作者也可以定義自己的「自訂」屬性,這些屬性不會影響編譯,但仍附加至產生的 FIDL JSON 中繼表示法,以供下游使用。

現有屬性語法的三個屬性缺少:

  • 多個屬性是以以半形逗號分隔的單一宣告的形式編寫,例如:[Discoverable, NoDoc, Transport = "Channel"]。這類屬性不具彈性,且使用者不清楚這些屬性是各自獨立的。
  • 屬性目前只能採用零或一個引數,但只能採用多個引數。
  • 當屬性接受引數時,此值一律為字串。例如,MaxBytes 屬性使用字串化數字做為引數,例如:[MaxBytes="128"]。這會造成混淆,尤其是在可預測的其餘 FIDL 類型時。

FIDL 語法目前正在 RFC-0050: Syntax Revamp 工作進行重大遷移。這也是為屬性實作屬性變更的好機會。

設計

語法

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

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

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

const DEFAULT_TRANSPORT string = "Channel";

@transport(DEFAULT_TRANSPORT)
protocol P {};

引數必須以低_snake_cased「keyword」語法表示,類似於在 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 組合,後者會採用 #/definitions/constant 的結構定義。如果屬性只接受一個引數,且來源中沒有關鍵字 name,則單一引數的名稱會預設為 "value"

此外,compose stanzas 和 reserved 資料表/聯集成員之前也無法傳送屬性。此 RFC 會修正這些監督作業,在 #/definitions/interface 中新增一個 #/definitions/compose 定義,並在正式將人工屬性附加到 #/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 屬性的參考說明文件,以反映此設計所做的規則,並注意每個屬性的有效引數類型和默示預設值。

缺點、替代方案和未知

In-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 {...};

這項設計具有「吃自己的 Dogfood 測試」優點: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." 是轉換為整數的有效值,但另一個後端則不行。要找出這個 ilk 的所有細微差異並不容易,導致後端絕對不同的屬性實作。更加瞭解屬性類型在傳真中行為如何排除這類不一致的一個標準。

已拒絕的次要修改

本文件所述的屬性語法明確指出,單一宣告的多個屬性之間的排序不相關。另一種做法是強制使用字母順序。這個遭拒,因為作者定義的自訂屬性和日後的 fidlc 原生屬性可能會以特定方式參照彼此,但按照特定排序方式為求明確。請考量下列兩個作者定義的自訂屬性;屬性按照字母順序排列時,順序可能會讓使用者感受到反向混淆:

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

此外,將駝峰式大小寫視為屬性語法的建議大小寫慣例。這項建議最終遭到拒絕,因為 FIDL 樣式指南中未實作駝峰式大小寫建議中的其他大小寫建議,而且對於單獨新增屬性的大小寫樣式也被認定為造成了莫大的混淆。

既有圖片與參考資料

這個 RFC 是 RFC-0050:語法 Revamp 所定義的語法的演變,因此會修改正式的 FIDL Grammar

本提案以其他語言編寫許多「類屬性」的現有實作項目,具體說明如下:

  • Python 的裝飾器語法關鍵字引數設計可以做為本文件中部分設計選擇的靈感,例如使用 @ 符號以及透過關鍵字參照引數。
  • Rust 的「屬性規格」雖然部分語法選項略有不同,但在概念上也類似於這個提案。
  • 我們也會將 Cap'n Proto 的註解結構定義視為本文件所提議語法的另一個功能作為參考。