| 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++ 生成的绑定中的 build 失败来捕获。随着 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 以外的平台,并实现更纯粹的编程。
- 缺点:作用域规则更复杂,样式不受强制执行,但建议执行(例如通过 linting)。 可能会导致合作伙伴构建的 API 不符合我们想要的 Fuchsia 样式指南(因为它们不需要运行或遵守 linting)。
- 直接在语言中强制执行样式限制,从而消除此类问题。
- 优点:强制执行样式,告知开发者应如何操作,否则无法编译。
- 缺点:在语言定义中融入了样式选择,对于使用 FIDL 的新手开发者来说,需要付出更多努力。
- → 我们拒绝了该提案,而是倾向于直接在语言中强制执行样式的做法。
- → 下一步是正式提出提案,以实现此目标,并阐明此目标的所有方面(例如,
uint8是否应为Uint8,vector<T>是否应为Vector<T>?)
我们决定推翻此决定,依据如下:
内核 API 现在通过 FIDL 进行描述。这促使我们在去年 10 月重新提出标识符唯一性问题,并从根本上推翻了之前的决定,允许“C 样式”和“FIDL 样式”共存。这已由今天的 FIDL Linter 检查。
我们还看到其他用例推动 FIDL 来泛化传输,并可能还推动本地样式规则来更好地支持这些网域。
标识符冲突仍然是一个问题,并且在 FIDL 工具链中,建模目标语言限制是一个已明确发现的缺口。
在先技术和参考资料
在 proto3 中,应用了类似的规则来生成 JSON 编码的 lowerCamelCase 名称。