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

RFC-0086:RFC-0050 更新:FIDL 属性语法
状态已接受
区域
  • FIDL
说明

为 FIDL 语言中的属性指定了新的语法。

问题
Gerrit 更改
作者
审核人
提交日期(年-月-日)2021-03-09
审核日期(年-月-日)2021-04-07

摘要

本文档介绍了 FIDL 语言中属性的新语法。

另见:

设计初衷

FIDL 属性提供了一种将编译时元数据附加到 FIDL 声明的明确方式,允许作者向 FIDL 编译器传递与文档 (DocNoDoc) 编译时验证 (ForDeprecatedCBindings)、代码生成 (TransitionalTransport)、所需的 API 可用性 (Discoverable) 等相关的额外信息。除了这些“官方”属性之外,FIDL 作者还可以定义自己的“自定义”属性,这些属性不会影响编译,但仍会附加到生成的 FIDL JSON 中间表示法,以供可能的下游使用。

现有属性语法缺少以下三个属性:

  • 多个属性应编写为一个逗号分隔的声明,如下所示:[Discoverable, NoDoc, Transport = "Channel"]。这种方法不灵活,而且可能不清楚这些属性是否相互独立。
  • 属性目前只能接受零个或一个参数,不能接受更多参数。
  • 如果属性接受参数,则值始终为字符串。例如,MaxBytes 属性会将字符串化的数字作为其参数,如下所示:[MaxBytes="128"]。这会令人困惑,尤其是当 FIDL 的其余部分具有可预测的类型时。

作为 RFC-0050:语法改进工作的一部分,FIDL 语法目前正在进行重大迁移。这也为实现属性的语法更改提供了绝佳机会。

设计

语法

每个属性都是一个单独的声明,属性名称(按惯例采用小写蛇形命名法)直接位于 @ 符号之前。对于包含多个属性的声明,属性之间没有首选排序。例如,属性声明 [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_case 格式的“关键字”语法表示,但仅接受一个参数的属性除外,此类属性必须省略关键字。只有 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 属性的参考文档将更新,以反映此设计规定的规则,并注明每个属性的有效实参类型和隐式默认值。

缺点、替代方案和未知

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

此外,驼峰式命名法被视为属性语法的推荐命名惯例。此建议最终被拒绝,因为 FIDL 样式指南中的任何其他大小写建议都没有采用驼峰式,并且我们认为仅为属性添加新的大小写样式会造成过度混淆。

在先技术和参考文档

本 RFC 是对 RFC-0050:语法改进中定义的语法的演变,因此会修改正式的 FIDL 语法

此提案借鉴了其他语言中的一些现有“类属性”实现,具体而言:

  • Python 的修饰符语法关键字参数设计为本文档中的一些设计选择提供了灵感,例如使用 @ 符号和按关键字引用参数。
  • Rust 的属性规范在某些语法选项上表面上有所不同,但在概念上与此提案也相似。
  • Cap'n Proto 的注解架构被视为本文档中提出的语法的潜在替代方案,具有更多功能。