RFC-0086:RFC-0050:FIDL 属性语法更新

RFC-0086:RFC-0050 的更新:FIDL 属性语法
状态已接受
区域
  • 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 {};

实参必须使用 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:标识符唯一性适用于属性名称。这意味着,在规范名称解析期间,属性名称中的不同大小写和连续下划线会缩减为单个通用名称。因此,以下示例会导致范围冲突,并在编译时生成错误:

@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 节和 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 输出中 type Smaybe_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 {...};

此设计的好处是“以身作则”: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 的 注解架构 被 视为此文档中提出的语法的潜在功能更丰富的替代方案的 参考点。