| RFC-0086:对 RFC-0050 的更新:FIDL 属性语法 | |
|---|---|
| 状态 | 已接受 |
| 区域 |
|
| 说明 | 为 FIDL 语言中的属性指定了新语法。 |
| 问题 | |
| Gerrit 更改 | |
| 作者 | |
| 审核人 | |
| 提交日期(年-月-日) | 2021-03-09 |
| 审核日期(年-月-日) | 2021-04-07 |
摘要
本文档介绍了 FIDL 语言中属性的新语法。
另见:
设计初衷
FIDL 属性提供了一种清晰的方式,可将编译时元数据附加到 FIDL 声明,从而允许作者向 FIDL 编译器传递与文档(Doc、NoDoc)、编译时验证(ForDeprecatedCBindings)、代码生成(Transitional、Transport)、所需的 API 可用性(Discoverable)等相关的额外信息。除了这些“官方”属性之外,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 {};
实参必须使用与 Python 中类似的 lower_snake_cased“关键字”语法来表示,但仅接受一个实参的属性除外,这些属性必须省略关键字。仅针对 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:标识符唯一性适用于属性名称。这意味着,在规范名称解析期间,属性名称中的不同大小写和连续下划线会缩减为单个通用名称。因此,以下示例会导致范围冲突,并在编译时生成错误:
@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 stanza 和 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 示例,其 JSON IR 输出中针对 type S 的 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,该属性接受可选实参 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 属性的参考文档将更新,以反映此设计规定的规则,并注明每个属性的有效实参类型和隐含默认值。
缺点、替代方案和未知因素
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 {...};
这种设计的好处是“自食其狗粮”: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 语法。
此提案借鉴了其他语言中的许多现有“类似属性”的实现,具体包括: