RFC-0050:FIDL 语法修订

RFC-0050:FIDL 语法修订
状态已接受
领域
  • FIDL
说明

我们确立了语法选择的指导原则,并根据这些原则进行了一些语法更改。

作者
  • pascallouis@google.com
提交日期(年-月-日)2020-02-26
审核日期(年-月-日)2019-06-04

摘要

我们确立了语法选择的指导原则,并根据这些原则进行了一些语法更改。

变更

  • 将类型置于第二位,例如,在方法参数名称中,将类型置于其各自的类型之前;在表声明中,成员名称位于其各自的类型之前;
  • 将类型更改为将布局与约束条件分开,以将与布局相关的类型信息放置在 : 分隔符的左侧,而约束条件信息位于右侧,例如 array<T, 5>vector<T>:5 更清楚地表示数组的大小对布局有影响,而它是向量的约束条件。
  • 推出匿名布局。例如,table { f1 uint8; f2 uint16; } 可以直接在方法参数列表中使用。
  • 顶级类型的声明是通过在 type Name = Layout; 形式的类型简介声明的帮助下使用匿名布局完成的。
  • 最后,对于协议 P,将 Prequest<P> 分别重命名为 client_end:Pserver_end:Prenaming请注意,该协议是客户端或服务器端的约束条件,而不是之前的位置,后者会错误地指示与布局相关的问题。

与其他 RFC 的关系

此 RFC 后来修改了:

设计初衷

入门示例

代数数据类型

语法灵活多变,无需任何额外的糖分,就能流畅地表示代数数据类型 (ADT)。例如:

/// Describes simple algebraic expressions.
type Expression = flexible union {
    1: value int64;
    2: bin_op struct {
        op flexible enum {
            ADD = 1;
            MUL = 2;
            DIV = 3;
        };
        left_exp Expression;
        right_exp Expression;
    };
    3: un_op struct {
        op flexible enum {
            NEG = 1;
        };
        exp Expression;
    };
};

在模式方面,我们选择使用 structunionunion 提供可扩展性,因此不需要(也不需要)使用更严格的变体。如果需要更改变体,我们可以改为添加新的批量变体,并迁移到使用此新变体。(在需要演变的其他地方(例如,二元或一元运算符列表时,可以选择灵活的枚举)。

如需支持 ADT,除了符合人体工程学的语法来描述数据类型之外,例如,预期的关键特性之一是易于构造和销毁(例如通过模式匹配或访问者模式)。

此 RFC 不会为 FIDL 引入新功能,并且由于递归类型的限制,该示例现在无法编译。我们计划添加对通用递归类型的支持,并且此扩展程序将成为未来 RFC 的对象。

更轻松地合并不可改进的消息和可改进的消息

例如,表示一个既具有结构体元素(紧凑型、内嵌式、快速编码/解码)又具有扩展可能性的“可扩展结构体”:

type Something = struct {
    ...

    /// Provide extension point, initially empty.
    extension table {};
};

例如,fuchsia.test.breakpoints 库需要定义一个配音为 Invocation 的可扩展事件。这些事件均具有共同的值,并且具有每个事件变体的特定载荷。现在,它可以更直接、更简洁地表示为:

type Invocation = table {
    1: target_moniker string:MAX_MONIKER_LENGTH;
    2: handler Handler;
    3: payload InvocationPayload;
};

type InvocationPayload = union {
    1: start_instance struct{};
    2: routing table {
        1: protocol RoutingProtocol;
        2: capability_id string:MAX_CAPABILITY_ID_LENGTH;
        3: source CapabilitySource;
    };
};

可扩展的方法参数

例如,可扩展的方法参数:

protocol Peripheral {
    StartAdvertising(table {
        1: data AdvertisingData;
        2: scan_response AdvertisingData;
        3: mode_hint AdvertisingModeHint;
        4: connectable bool;
        5: handle server_end:AdvertisingHandle;
    }) -> () error PeripheralError;
};

对参数使用 table 并非“最佳做法”。这种做法可能没什么问题,但也存在一系列问题,例如,N 个字段可能存在 2N 种可能性,这可能会增加收件人的复杂性。

指导原则

FIDL 主要关注定义应用二进制接口 (ABI) 问题,其次关注应用编程接口 (API) 问题。这可能会导致生成的语法比其他编程语言所习惯的更详细,也可能更符合预期。例如,联合的 unit 变体将表示为一个空结构体,如上面的 InvocationPayload 示例所示。我们可以选择引入语法糖来避免此类型,但这样就不太容易将 ABI 问题放在中心位置。

将布局与约束条件分离

语法一致

    layout:constraint

对于类型(即控制布局的所有内容均位于冒号前面),控制约束条件的所有内容均位于冒号后面。布局描述了字节的布局方式和解释方式。约束条件限制了给定布局可以表示的内容,它是编码/解码期间完成的验证步骤。

此语法提供了一种简单的方法来考虑变更对 ABI 的影响,特别是需要遵循两条简写规则:

  1. 如果两种类型具有不同的布局,则无法在一种类型之间进行软转换,反之亦然1,也就是说,更改左侧会破坏 ABI
  2. 约束条件可能会演变,只要写入者比读取器更受约束,一切就可以兼容,即可以在右侧改进并保留 ABI

下面是遵循此原则的示例更改:

  • array<T>:N 变为 array<T, N>
  • handle<K> 变为 handle:K
  • vector<T>? 变为 vector<T>:optional
  • Struct? 变为 box<Struct>
  • Table? 变为 Table:optional
  • Union? 变为 Union:optional

本 RFC 的设计部分讨论了这些变更。

首先采用二进制传输格式

虽然许多格式可以表示 FIDL 消息,但 FIDL 线缆格式(或“FIDL 二进制线格式”)是优先处理的格式,应优先考虑。

这意味着,旨在使语法一致性与 ABI 一致性保持一致的语法选择应考虑采用二进制传输格式(而不是 JSON 等其他格式)下的 ABI。

例如,当涉及类型的 ABI 时,名称无关紧要 - 名称对于协议和方法很重要。虽然名称可能对可能的 JSON 格式很重要,但在进行语法选择时,我们会选择过度轮替到二进制 ABI 格式,如果这会妨碍对 ABI 规则的理解,则不会更改语法以利用文本表示法。

功能较少

在 Wright 的“form and function should be one”中,使我们尽量使外观相似的结构具有相似的含义,反之亦然。例如,在内部利用信封的所有可扩展数据始终以 ordinal: 显示。

layout {
    ordinal: name type;
};

我们会尽量减少功能和规则,并希望组合功能以实现各种应用场景。在实践中,在考虑新功能时,我们应该首先尝试调整或泛化其他现有特征,而不是引入新功能。例如,虽然可以为可扩展的方法参数(和返回)设计特殊语法(如 RFC-0044:可扩展方法参数中所述),但我们更倾向于利用 table 以及适用于这些内容的常规语法。

有人可能会认为,我们甚至应该要求对方法请求和响应采用匿名 struct 布局,而不是为从大多数编程语言借用的参数使用当前的语法糖。不过,一种竞争性设计注意事项是帮助库作者在总体上实现一致性:在 enum 布局声明中,我们更喜欢语法糖,而不是明确选择封装类型,因为具有合理的默认值可以为 FIDL 库中的枚举提供更高的一致性。这反过来又提供了用于后续切换枚举的迁移路径,例如,如果库定义了通用 ErrorStatus 枚举,以后可以将其替换为另一个“更好”的通用 ErrorStatusV2

设计

类型

类型遵循常规形式:

Name<Param1, Param2, ...>:<Constraint1, Constraint2, ...>

空类型的参数化必须省略 <>,即 uint32(而不是 uint32<>)。

没有约束条件的类型必须同时省略 : 分隔符和 <>,即 uint32(而不是 uint32:<>uint32:)。

具有单个限制条件的类型可以省略 <>,也就是说,允许 vector<uint32>:5vector<uint32>:<5> 且两者是等效的。

内置

支持以下基元类型

  • 布尔值 bool
  • 有符号整数 int8int16int32int64
  • 无符号整数 uint8uint16uint32uint64
  • IEEE 754 浮点 float32float64

固定大小的重复值

array<T, N>

可以将其视为 struct,其中包含类型为 TN 元素。

大小可变的重复值

vector<T>
vector<T>:N

也就是说,尺寸 N 可以省略。

大小可变的 UTF-8 字符串

string
string:N

也就是说,尺寸 N 可以省略。

对内核对象(即句柄)的引用

handle
handle:S

其中,子类型 Sbtibufferchanneldebuglogeventeventpairexceptionfifoguestinterruptiommujobpagerpcidevicepmtportprocess、{2、{2、}、{2、/}、{2、/}、{2、/}、profileresourceprofileresourceprofileresourceresourcesocketsuspendtokenthreadtimervcpuvmarvmo

RFC-0028:标识名权限中引入的权限的句柄:

handle:<S, R>

其中权利 R 是权利值或权利表达。

对协议对象的引用,即目标用途的通道句柄

client_end:P
server_end:P

client_end:fuchsia.media.AudioCoreserver_end:fuchsia.ui.scenic.Session

具体而言,单独引用协议是不合法的:协议声明不会引入某种类型,而只会引入可视为客户端或服务器端的类型。传输泛化部分对此进行了更详细的介绍。

布局

除了内置布局之外,我们还有五种布局,它们可以配置为引入新类型:

  • enum
  • bits
  • struct
  • table
  • union

有限布局

enumbits 布局的表示方式类似:

layout : WrappedType {
    MEMBER = expression;
    ...;
};

其中,: WrappedType 是可选的 [^2];如果省略,则默认为 uint32

enum 示例:

enum {
    OTHER = 1;
    AUDIO = 2;
    VIDEO = 3;
    ...
};

bits 示例:

bits : uint64 {
    TOTAL_BYTES = 0x1;
    USED_BYTES  = 0x2;
    TOTAL_NODES = 0x4;
    ...
};

灵活布局

tableunion 布局的表示方式类似:

layout {
    ordinal: member_name type;
    ...;
};

在这里,ordinal: 可以视为语法糖,用于描述 envelope<type>

对于表,成员通常称为字段。对于联合,成员通常称为变体。此外,会员还可以通过以下方式预订:

layout {
    ordinal: reserved;
    ...
};

刚性布局

唯一的硬性布局 struct 以接近灵活布局的方式表示,没有灵活的表示法:

layout {
    member_name type;
    ...;
};

对于结构体,成员通常称为字段。

属性

布局前面可以有该布局的属性:

[MaxBytes = "64"] struct {
    x uint32;
    y uint32;
};

这样,您就可以明确地将属性附加到布局的成员以及该成员的类型:

table {
    [OnMember = "origin"]
    1: origin [OnLayout] struct {
        x uint32;
        y uint32;
    };
};

在引入一种新类型(即布局)的情况下,新引入的类型有两个可能的属性放置位置:

  • 针对新类型:[Attr] type MyStruct = struct { ... }
  • 在布局上:type MyStruct = [Attr] struct { ... }

fidlc 会认为这些等效项,如果这两个地方都指定了属性,则会引发错误。

无论使用哪个放置位置指定属性,属性在概念上都会附加到布局本身,而不是整个类型节。一个实际应用的例子是,在任何 IR 中,偏好设置都是将类型 stanza 上的属性降低到布局,而不是将布局上的属性向上提升到类型 stanza。

命名上下文和使用布局

布局本身不带有名称,从某种意义上说,所有布局都是“匿名”的。而是布局的特定用途,决定了它在目标语言中的名称。

例如,布局最常见的用途是引入一个新的顶级类型:

library fuchsia.mem;

type Buffer = struct {
    vmo handle:vmo;
    size uint64;
};

此处,结构体布局用于顶级库中的“新类型”声明。

介绍性说明中介绍了匿名上下文中的用法示例,用来表示可扩展的方法参数:

library fuchsia.bluetooth.le;

protocol Peripheral {
    StartAdvertising(table {
        1: data AdvertisingData;
        2: scan_response AdvertisingData;
        3: mode_hint AdvertisingModeHint;
        4: connectable bool;
        5: handle server_end:AdvertisingHandle;
    }) -> () error PeripheralError;
};

此处,在 Peripheral 协议声明中,StartAdvertising 方法的请求内使用表格布局。

我们指的是按最不具体到最具体的名称列表,其识别的是布局的使用作为其“命名上下文”。在上面的两个示例中,我们分别使用了 fuchsia.mem/Bufferfuchsia.bluetooth.le/Peripheral, StartAdvertising, request 作为两个命名上下文。

在 JSON IR 中,布局声明将包括其命名上下文,即上述名称分层列表。

命名上下文

在库 some.library 中,type Name = 声明引入了 some.library/Name 的命名上下文。

ProtocolMethod 的请求(分别是响应)中使用时,会引入 some.library/Protocol, Method, request/response 的命名上下文

在布局中使用会将字段名称(或变体名称)添加到命名上下文中。例如:

type Outer = struct {
    inner struct {
        ...
    };
};

第一个外部结构体布局的命名上下文为 some.library/Outer,第二个内部结构体布局的命名上下文为 some.library/Outer, inner

生成的扁平化名称

许多目标语言可以分层表示命名上下文。例如,在 C++ 中,一个类型可以在封装类型中定义。但是,某些目标语言没有这种能力,因此,我们必须考虑扁平化命名上下文而导致的名称冲突。

例如,请考虑命名上下文 some.library/Protocol, Method, request。这可能会在 Go 中展平为 some.library/MethodRequestOfProtocool。如果其他一些定义碰巧使用了命名上下文 some.library/MethodRequestOfProtocool,那么 Go 绑定就会面临一个难题:必须重命名两个声明中的一个。最糟糕的是,如果一个声明(无名称冲突)的库演变为包含两个声明(名称冲突)的库,那么 Go 绑定必须与之前生成的绑定一致,以避免破坏源代码的更改。

我们的经验表明,此类决策最好留给核心 FIDL 编译器,而不是将工具链委托给 FIDL 绑定。因此,我们将计算并保证稳定的扁平化名称。

在 JSON IR 中,命名上下文将包含生成的扁平化名称,编译器可保证该名称在全局范围内是唯一的,即前端编译器负责生成扁平化名称,并验证扁平化名称是否与其他声明(无论是其他扁平化名称或顶级声明)冲突。

例如,如果库作者添加了一个声明 type MethodRequestOfProtocool = ...,而该声明与另一个声明的生成的扁平化名称冲突,编译将会失败。

通过绑定使用命名上下文

绑定大致可分为两类:

  1. 能够使用目标语言表示命名上下文范围,例如 C++ 语言的绑定;
  2. 无法表示命名上下文,并且回退到使用生成的扁平化 Nuse 扁平化名称,例如适用于 Go 语言的绑定。

这比目前的情况有所改进,因为我们至少将在绑定之间保持一致,并在前端获得编译器帮助。今天,我们必须在游戏后期(后端)生成一些名称,这种方法具有危险且容易出错。

以下面的定义为例:

type BinOp = union {
    add struct {
        left uint32;
        right uint32;
    };
};

在 C++ 绑定中,我们可能最终会得到以下结果:

class BinOp {
    class Add {
        ...
    };
};

变体 add 的访问器将为:

BinOp.add();

这与类的定义不冲突。

或者,在 Go 中,使用以下扁平化名称:

type BinOp struct { ... };
type BinOpAdd struct { ... };

如果库作者稍后决定引入名为 BinOpAdd 的顶级声明,前端编译器捕获该声明并报告为错误。库作者可以掌控这项变更的后果,并且可以选择破坏源代码兼容性以引入这一新声明。同样,这比当前情况有所改进,即稍后发现此类源代码兼容性破坏的情况,并且距离做出决定的位置更远。

类型别名和新类型

RFC-0052:类型别名和新类型中,我们改进了类型别名和新的类型声明。

别名声明为:

alias NewName = AliasedType;

即,与 RFC-0052 中建议的语法相比没有变化。

新类型声明为:

type NewType = WrappedType;

也就是说,无论封装类型是另一个现有类型(封装)还是某种布局(新的顶级类型),新类型的语法都相同。这与 RFC-0052 中最初建议的语法不同。

可选性

某些类型本身就是可选的:vectorsstringsenvelopes 以及使用此类构造的布局,即 table(信封的矢量)和 union(是标记加信封)。因此,无论这些类型是否是可选的,都是一种限制条件,并且可以演化为(通过放宽约束条件变为可为 null)或演化为(通过收紧限制条件成为必需类型)。

另一方面,int8struct 布局等类型本身不能成为可选类型。为了具有可选性,需要引入间接引用,例如在结构体用例中通过间接引用引入。因此,与本身是可选的类型不同,没有演化路径不可能。

为了区分这两种情况,并遵循将 ABI 问题保持“左侧”和可演化问题“位于右侧”的原则,需要:

当然是可选 非自然可选项
string:optional box<struct>
vector:optional
union:optional

在命名时,最好使用“可选”“必需”“存在”“不存在”等字词。 (We should avoid "nullable", "not nullable", "null fields".) 根据该命名偏好,我们选择 box<T> 而不是 pointer<T>box 默认为可选结构,即新语法中的 box<struct> 等同于旧语法中的 struct?,而 box<struct>:optional 是多余的,可能会触发编译器或 linter 的警告。为了更好地符合我们预期的用例:用户通常将结构体装入方框以获得可选性,而不是添加间接。

常量

常量声明为:

const NAME type = expression;

限制条件排序

根据布局和约束条件对类型进行参数化时,对于给定的类型,这些参数的顺序是固定的。此 RFC 定义了以下约束条件顺序(尚无多个布局参数的类型):

  • 标识名:子类型、权限、可选。
  • 协议 client/server_end:协议、可选。
  • 矢量:大小、可选性。
  • 联合:可选。

作为指导原则,可选性始终在最后,对于标识名,子类型在权利之前。

例如,请考虑以下结构体,并对其成员定义了所有可能的约束条件:

type Foo = struct {
  h1 zx.handle,
  h2 zx.handle:optional,
  h3 zx.handle:VMO,
  h4 zx.handle:<VMO,optional>,
  h5 zx.handle:<VMO,zx.READ>,
  h6 zx.handle:<VMO,zx.READ,optional>,
  p1 client_end:MyProtocol,
  p2 client_end:<MyProtocol,optional>,
  r1 server_end:P,
  r2 server_end:<MyProtocol,optional>,
  s1 MyStruct,
  s2 box<MyStruct>,
  u1 MyUnion,
  u2 MyUnion:optional,
  v1 vector<bool>,
  v2 vector<bool>:optional,
  v3 vector<bool>:16,
  v4 vector<bool>:<16,optional>,
};

未来方向

除了更改现有功能的语法之外,对于预计在近期内看到光明的特征,我们还会寻找并设置方向。此处的重点是预期的表现力和语法渲染(而不是精确的语义,这需要单独的 RFC)。例如,虽然我们介绍了传输泛化,但并未讨论各种棘手的设计问题(例如可配置性程度、JSON IR 中的表示法)。

此部分还应被视为方向性规范,而不是未来的规范。引入新功能时,系统将评估其对应的语法以及这些功能的精确工作方式。

上下文名称解析

例如

const A_OR_B MyBits = MyBits.A | MyBits.B;

会简化为:

const A_OR_B MyBits = A | B;

例如

zx.handle:<zx.VMO, zx.rights.READ_ONLY>

会简化为:

zx.handle:<VMO, READ_ONLY>

约束条件

声明网站限制

type CircleCoordinates = struct {
    x int32;
    y int32;
}:x^2 + y^2 < 100;

使用网站限制条件

type Small = struct {
    content fuchsia.mem.Buffer:vmo.size < 1024;
};

独立限制条件

constraint Circular : Coordinates {
    x^2 + y^2 < 100
};

信封约束

表和可扩展联合的语法隐藏了信封的使用:

  • tablevector<envelope<...>>,并且
  • union 是一种 struct { tag uint64; variant envelope<...>; }

目前,tableunion 声明中出现的 ordinal: 是唯一包含信封的位置,因此不妨将此语法视为信封的“糖化”简介。从本质上讲,我们可以按以下方式实现脱糖:

脱糖表和灵活联合
table ExampleTable {
    1: name string;
    2: size uint32;
};
table ExampleTable {
    @1 name envelope;
    @2 size envelope;
};
union ExampleUnion {
    1: name string;
    2: size uint32;
};
union ExampleUnion {
    @1 name envelope;
    @2 size envelope;
};

如果我们想要限制 envelope(比方说 require 一个元素),则需要对序数 ordinal:C 施加此约束条件,例如:

脱糖表和灵活联合
table ExampleTable {
    1:C1 name string:C2;
    2:C size uint32;
};
table ExampleTable {
    @1 name envelope:C1;
    @2 size envelope:C;
};
union ExampleUnion {
    1:C1 name string:C2;
    2:C size uint32;
};
union ExampleUnion {
    @1 name envelope:C1;
    @2 size envelope:C;
};

属性

FIDL 的类型系统已经包含约束条件概念。使用 vector<uint8>:8 表示向量最多包含 8 个元素,或使用 string:optional 放宽可选性限制并允许字符串是可选的。

各种需求都迫使我们同时追求更具表现力的限制条件,并追求如何统一和处理这些限制条件。

例如,fuchsia.mem/Buffer 会注明“此大小不得大于 VMO 的物理大小”。我们正在努力引入 RFC-0028:句柄权限,即限制句柄。或者考虑要求提供表字段,即限制在其他可选信封上的显示内容。

目前,无法描述被操纵的值或实体的运行时属性。虽然 string 值有大小,但无法命名。虽然 handle 拥有与其相关联的权利,但也无法为其命名。

为了正确解决与受限类型相关的表现力问题,我们必须先将值的运行时方面与这些值的 FIDL 包含的有限视图进行桥接。我们计划引入 **属性 **,这些属性可以视为附加到值的虚拟字段。属性对传输格式没有任何影响,它们纯粹是一种语言级结构,并且出现在 JSON IR 中,用于绑定,以便为其指定运行时含义。属性的唯一目的是表达对它们的约束条件。每个属性都需要与绑定已知,采用方式与内置为绑定已知。

继续上面的示例,string 值可以具有 uint32 size 属性,句柄可以具有 zx.rights rights 属性。

例如:

layout name {
    properties {
        size uint32;
    };
};

传输泛化

声明新的传输至少需要定义一个新名称,指定该传输支持的消息的约束条件(例如“无句柄”“无表”),并为协议指定约束条件(例如,只有“即发即弃方法”和“无事件”)。

构想的语法与无类型 FIDL Text 中的配置相似:

transport ipc = {
    methods: {
        fire_and_forget: true,
        request_response: true,
    },
    allowed_resources: [handle],
};

之后又用作:

protocol SomeProtocol over zx.ipc {
    ...
};

处理泛化

目前,句柄是完全特定于 Fuchsia 的概念:它们与 Zircon 内核直接关联,映射到 zx_handle_t(或除 C 之外的其他语言),其种类只是内核公开的对象,例如 portvmofifo 等。

在考虑其他情况(例如,进程通信中)时,一种理想的扩展点是能够直接在 FIDL 中定义句柄,而不是将其纳入语言定义。

例如,定义 zircon 手柄:

library zx;

resource handle : uint32 {
    properties {
        subtype handle_subtype;
        rights rights;
    };
};

type handle_subtype = enum {
    PROCESS = 1;
    THREAD = 2;
    VMO = 3;
    CHANNEL = 4;
};

type rights = bits {
    READ = ...;
    WRIE = ...;
};

这将允许 handlehandle:VMO(或其他库 zx.handle:zx.handle.VMO)。

我们存在一个实验性实现,该实现将用于打破 Zircon 和 FIDL 之间的循环依赖关系(在此变更之前,Zircon 的 API 用 FIDL 进行描述,但 FIDL 部分地根据 Zircon 的 API 进行了定义)。

实施策略

临时“版本声明”将添加到所有 .fidl 文件的顶部,以供 fidlc 用于检测 .fidl 文件采用的是旧语法还是新语法。

此令牌将紧挨着库语句之前:

// Copyright notice...

deprecated_syntax;

library fidl.test;
...

最好使用显式标记,以简化 fidlc 在检测语法方面的作用并提高可读性。检测语法所面临的挑战的一个示例是,解释为任何一种语法都会导致编译错误。这些场景需要启发式算法来在旧语法和新语法之间做出选择,这可能会导致令人惊讶的结果。

此外,此令牌会以之前的语法(而不是新语法)(例如 new_syntax;")添加到所有文件,以方便在即将进行迁移的方面进行社交。FIDL 文件的读取者会感觉到语法即将发生变化,并可以通过其他渠道(例如文档、邮寄名单)查找其他上下文。

系统会添加一个新的 fidlconv 主机工具,该工具可以接受旧格式的 FIDL 文件并将其转换为新格式的文件,在本节中称为 .fidl_new。虽然此工具独立于 fidlc,但它需要利用编译器的内部表示法才能正确执行这种转换。例如,仅当类型 Foo 是协议时,才需要转换为 client_end:Foo,以便确定情况 fidlconv 是否会先利用 fidlc 编译 FIDL 库。

我们将对 FIDL 前端编译器 fidlc 以及格式化程序和 linter 等配套工具进行扩展,以支持基于上面定义的标记这两种语法。

通过这项新增功能,构建流水线将按以下方式进行扩展:

可视化:构建流水线策略

即:

  • fidlconv 工具会将采用旧语法的 FIDL 文件转换为新语法。
  • fidlc 编译器会通过编译旧语法来输出 .json
  • fidlc 编译器会通过单独编译新语法来输出 .json IR。
  • fidlfmt 格式设置工具会对生成的新库文件 .fidl_new 进行格式化。

对于测试和验证:

  • 系统将对这两个 json IR 进行比较,并验证它们是否匹配(span 信息除外)。
  • 系统将验证新库文件格式的幂等性,以使用新语法检查 fidlc 编译器的输出和 fidlfmt 格式设置器的输出。

在此实现过程中,FIDL 团队还将编码表后端转为独立的二进制文件(与其他后端相同),然后通过生成最后的使用并在 fuchsia.git 树代码库中进行检查来弃用并删除 C 绑定后端。

工效学设计

此 RFC 完全与工效学设计有关。

我们愿意让熟悉当前语法的开发者在短期内降低工作效率,让他们重新训练使用这种经过修改的语法,因为我们坚信,今后会有更多使用 FIDL 的开发者受益。

文档和示例

这将需要更改:

向后兼容性

此更改不向后兼容。如需了解过渡计划,请参阅“实现”部分。

性能

此更改对效果没有影响。

安全性

此更改对安全性没有任何影响。

测试

请参阅转换计划的实现部分,并验证其正确性。

缺点、替代方案和未知因素

使用英文冒号将名称与类型分隔开

由于我们要将类型移至第二,因此我们还可以考虑使用非常常见的 : 分隔符,如类型理论、Rust、Kotlin、机器学习语言(SML、Haskell、OCaml)、Scala、Nim、Python、TypeScript 等:

    field: int32 rather than the proposed field int32

该提案拒绝使用该方法。

: 分隔符主要用于将布局与约束条件分开。它还可用于指示 enumbits 声明的“封装类型”。最后,它用于在 tableunion 声明中表示信封。进一步使 : 分隔符重载,尤其是在语法上与其主要用途非常接近时,会导致混淆(例如,表成员 1: name: string:128;)。

省略分号

我们已讨论过如何省略终止声明的分号(无论是成员、常量或其他)。

此方案选择不探索这种简化过程。

移除英文分号对 FIDL 编写者在语法上几乎没有区别。这也不是要进行的关键更改,如果将来我们想要探索此功能,则更改起来非常简单(例如,Go 移除分号的方法)。

但是,如果存在用于终止成员和声明的分号,则可以更加轻松地保证语法规则明确,尤其是在我们探索限制条件(use-site 和声明 site)时。例如,使用 struct Example { ... }:C; 等声明网站布局约束条件 (C) 时,我们会恰当地划分 : 分隔符和 ; 终止符之间的约束条件。

统一枚举和联合

从类型理论的角度来看,枚举表示单位类型的总和,并集表示任意类型的总和。因此,我们很容易将这两个概念统一为一个概念。这是支持 ADT 的编程语言(如 ML 或 Rust)采用的方法。

不过,从布局的角度来看,仅单元类型(枚举)的和类型可以比可扩展的对应项(并集)更高效地表示。虽然两者都针对添加新成员提供可扩展性,但只有联合体才提供从单元类型(例如 struct {})到任何类型的可扩展性。这种可扩展性是以内嵌信封为代价的。

我们选择了一种实用方法,能够在两个结构的复杂性与特殊大小写枚举的性能优势之间取得平衡。

参考编号

语法

关于可扩展的方法参数

类型别名和命名类型时

Footnote2

虽然相较于明确选择封装类型,更喜欢语法简洁似乎有点奇怪,但合理的默认值可为 FIDL 库中的枚举提供更高的一致性。这提供了以后切换枚举的迁移路径,例如,如果库定义了通用 ErrorStatus 枚举,以后可以将其替换为另一个“更好”的通用 ErrorStatusV2


  1. 至少,您应该充分了解电线格式和谨慎程度,例如 https://fxrev.dev/360015