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

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

为采用 FIDL 语言的属性指定新的语法。

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

总结

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

另见:

设计初衷

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

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

  • 多个属性应编写为一个以英文逗号分隔的声明,例如:[Discoverable, NoDoc, Transport = "Channel"]。这种做法不够灵活,而且属性彼此独立也不太明确。
  • 属性目前只能接受 0 个或 1 个参数,但不能接受更多参数。
  • 如果属性接受实参,则值始终为字符串。例如,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 中类似的更低版本的“关键字”语法来表示,但仅接受一个参数的属性除外(必须省略关键字)。系统只会针对 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/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 示例,其 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" 转换为整数 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 的注解架构被视作参考点,可替代本文档中提出的语法,这种替代方案具有更强大的功能。