RFC-0040:标识符唯一性 | |
---|---|
状态 | 已接受 |
区域 |
|
说明 | FIDL 规范和前端编译器目前会根据简单的字符串比较来判断两个标识符是否不同。本文提出了一种新算法,该算法会考虑绑定生成器进行的转换。 |
作者 | |
提交日期(年-月-日) | 2019-04-08 |
审核日期(年-月-日) | 2019-05-14 |
"SnowFlake vs SNOW_FLAKE"
摘要
FIDL 规范和前端编译器目前会根据简单的字符串比较来判断两个标识符是否不同。这提出了一种新算法,该算法会考虑绑定生成器进行的转换。
设计初衷
语言绑定生成器会转换标识符,使其符合目标语言约束条件和样式,将多个 FIDL 标识符映射到单个目标语言标识符。这可能会导致意外冲突,在定位到特定语言之前不会显示。
设计
这提议对 FIDL 标识符引入一个限制,且没有任何现有库违反此限制。它不会更改 FIDL 语言、IR(目前1)、绑定、样式指南或评分标准。
实际上,标识符由一系列连接在一起的字词组成。连接字词的常用方法包括 CamelCase
(其中从小写转换为大写是字词边界)和 snake_case
(其中一个或多个下划线 [_
] 用于分隔字词)。
标识符应转换为规范形式以进行比较。这将是一个 lower_snake_case
表单,保留原始表单中的单词分隔符。在以下情况下,系统会将字词拆分:(1) 出现下划线时,(2) 从小写或数字转换为大写时,以及 (3) 从大写转换为小写之前。
在 FIDL 中,必须使用标识符的原始形式。因此,如果某个类型名为 FooBar
,则尝试将其引用为 foo_bar
是错误的。
有一个简单的算法可以执行此转换,如下所示(使用 Python 2):
def canonical(identifier):
last = '_'
out = ''
for i, c in enumerate(identifier):
is_next_lower = i + 1 < len(identifier) and identifier[i+1].islower()
if c == '_':
if last != '_':
out += '_'
elif (((last.islower() or last.isdigit()) and c.isupper())
or (last != '_' and c.isupper() and is_next_lower)):
out += '_' + c.lower()
else:
out += c.lower()
last = c
return out
下面是一些示例,以及它们在各种目标语言中的可能翻译:
FIDL 标识符 | 规范形式 | C++ | Rust | Go | Dart |
---|---|---|---|---|---|
foobar | foobar | foobar | foobar | Foobar | foobar_ |
foo_bar | foo_bar | foo_bar | foo_bar | FooBar | fooBar_ |
foo__bar | foo_bar | foo_bar | foo_bar | FooBar | fooBar_ |
FooBar | foo_bar | foo_bar | foo_bar | FooBar | fooBar_ |
fooBar | foo_bar | foo_bar | foo_bar | FooBar | fooBar_ |
FOOBar | foo_bar | foo_bar | foo_bar | FooBar | fooBar_ |
实施策略
前端编译器将更新为检查每个新标识符的规范形式是否与任何其他标识符的规范形式冲突。
下一个版本的 FIDL IR 应围绕规范名称(而非原始名称)进行整理,但原始名称将作为声明中的字段提供。如果我们能够在生成的绑定中消除未修改的名称,则可以从 IR 中移除原始名称。
工效学设计
这会对实际存在的 FIDL 语言约束条件进行编码。
文档和示例
FIDL 语言文档将更新,以说明此约束条件。 它将扩展为包含上文设计部分中的大部分内容。
由于此提案只是编码现有做法,因此示例和教程不会受到影响。
向后兼容性
任何因这项变更而无法正常运行的现有 FIDL 库都违反了我们的样式指南,并且无法与许多语言绑定搭配使用。这不会更改用于计算序数的标识符的形式。
性能
这对前端编译器造成的开销微不足道。
安全
无影响。
测试
我们将对 fidlc
中的规范化算法实现进行广泛测试。还将有 fidlc
测试,以确保在声明冲突的标识符时捕获错误,并确保必须使用原始名称来引用声明。
缺点、替代方案和未知情况
一种做法是无所作为。通常,我们会在非 C++ 生成的绑定中捕获这些问题,并将其视为构建失败。随着 Rust 在 fuchsia.git
中的使用越来越多,冲突渗透到其他花瓣的可能性就会降低。而这些问题已经非常罕见了。
规范化算法很简单,但有一个遗憾的失败情况:UPPER_SNAKE_CASE 标识符中的混合字母数字字词可能会损坏。例如,H264_ENCODER
→ h264_encoder
,但 A2DP_PROFILE
→ a2_dp_profile
。这是因为该算法会将数字视为小写字母。我们必须在数字转换为字母时进行分屏,因为 H264Encoder
应规范化为 h264_encoder
。不含小写字母的标识符可以视为特殊情况,仅在下划线处进行分隔,但这会增加算法的复杂性,也可能会增加心理模型的复杂性。
规范形式可以表示为字词列表,而不是 lower_camel_case 字符串。它们是等效的,在实践中,将它们作为字符串进行管理更为简单。
在生成序数时,我们可以使用标识符的规范形式。这会使其成为一项破坏性更改,而没有明显的好处。 如果未来有破坏序数的标志日,我们可以考虑进行相应更改。
初步拒绝和第二次审核
在 2019 年 4 月 18 日的首次审核中,此 FTP 被拒,原因如下。
- 关于如何解决这类问题的两种相反观点。
- 尝试对目标语言的约束条件进行建模,以便尽可能在 FIDL 中保持最大的灵活性,即使与推荐的样式不同也是如此。这就是此 FTP 采用的方法。
- 优点:保留了 FIDL 在 Fuchsia 之外的最终用途的灵活性,从编程语言的角度来看更纯粹。
- 缺点:作用域规则更复杂,系统不会强制执行样式,但会鼓励使用(例如通过 lint 检查)。可能会导致合作伙伴构建的 API 不符合我们希望的 Fuchsia 样式指南(因为它们不需要运行或遵循 linting)。
- 直接在语言中强制执行样式约束条件,从而消除此类问题。
- 优点:强制执行样式,告知开发者应如何处理,否则无法编译。
- 缺点:将样式选择根植在语言定义中,使用 FIDL 的新手开发者需要克服更高的障碍。
- → 我们拒绝了该提案,而更倾向于采用直接在语言中强制执行样式的做法。
- → 接下来要做的是提出正式提案来实现这一点,并阐明这方面的所有方面(例如,
uint8
应为Uint8
,vector<T>
应为Vector<T>
?)
我们之所以决定撤销此决定,是因为发现了以下情况:
内核 API 现在使用 FIDL 进行描述。这促使我们于去年 10 月重新打开了标识符唯一性问题,并基本上推翻了该决定,允许“C 风格”和“FIDL 风格”共存。目前,这由 fidl lint 检查。
我们发现,其他用例会推动 FIDL 对传输进行泛化,并且可能还会使用本地样式规则来更好地支持这些领域。
标识符冲突仍然是一个问题,并且对目标语言约束条件进行建模是 FIDL 工具链中一个已明确指出的缺口。
在先技术和参考文档
在 proto3 中,系统会应用类似的规则来生成 JSON 编码的 lowerCamelCase
名称。