RFC-0086:更新 RFC-0050:FIDL 屬性語法 | |
---|---|
狀態 | 已接受 |
區域 |
|
說明 | 指定 FIDL 語言中的新屬性語法。 |
問題 | |
Gerrit 變更 | |
作者 | |
審查人員 | |
提交日期 (年-月-日) | 2021-03-09 |
審查日期 (年-月-日) | 2021-04-07 |
摘要
本文將說明 Fidl 語言中屬性的新語法。
另請參閱:
提振精神
FIDL 屬性提供明確的方式,可將編譯時間中繼資料附加至 FIDL 宣告,讓作者將相關說明文件 (Doc、NoDoc)、編譯時間驗證 (ForDeprecatedCBindings)、程式碼產生 (Transitional、Transport)、所需 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_a
和 req_b
) 和兩個選用引數(opt_a
和 opt_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
,該屬性會採用可選的引數 platform
、since
、removed
、deprecated
和 note
。例如:
@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;
};
值得一提的是,參照編號平台版本的引數 (即 since
、removed
和 deprecated
) 也可能會採用特殊字串 "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 的註解結構定義視為參考點,以便提供功能更豐富的替代方案,取代本文件中提出的語法。