= GEP-8: 静态类型检查 :icons: font .Metadata **** [horizontal,options="compact"] *编号*:: GEP-8 *标题*:: 静态类型检查 *版本*:: 9 *类型*:: 功能 *状态*:: 最终 *评论*:: 在 Groovy 2.0 中交付并在后续版本中增强 *负责人*:: Cédric Champeau *创建日期*:: 2011-10-08 *最后修改日期* :: 2018-10-12 **** == 摘要:静态类型检查 本 GEP 引入了语言中的一项新功能,称为静态类型检查。对于来自静态类型语言(例如 Java)的开发人员来说,发现 Groovy 编译器在编译时不会抱怨以下情况通常会令人不安: * 当进行不同类型的赋值时 * 当方法不存在时 * 当属性或变量不存在时 * 当返回对象类型与方法签名不匹配时 * ... 所有这些都是无声的,因为 Groovy 语言的动态特性使得此类代码完全有效。然而,在某些情况下,开发人员可能希望 Groovy 表现得像静态类型语言,并让编译器提示此类“错误”。为此,Groovy 必须引入静态类型检查。 == 原理:静态类型检查与静态编译 区分静态类型检查和静态编译很重要。本 GEP 的目标是提供一个选项来开启静态类型检查(STC)。如果 STC 激活,编译器将更加冗长(您还会看到“挑剔”这个词),但最终,生成的字节码和运行时行为将与您未激活此模式时完全相同。这与 Groovy++ 等替代编译器有重大区别,后者会执行 STC 然后生成不同的字节码,从而产生不同的运行时语义。 本 GEP 的范围仅是静态类型检查器,因此应仅被视为一项允许开发人员编写静态检查代码的功能,例如,这是一种利用 Groovy 语法减少 Java 代码冗长同时仍获得强类型检查代码的优雅方式。最终,IDE 可以支持 STC 模式并向开发人员提供信息。 === 实现细节 ==== 开发分支 自 Groovy 2.0-beta-2 以来,代码已合并到 master 分支。然而,如果对类型检查器进行了大量开发,建议在 grumpy 分支上工作。它添加了一个名为 TypeChecked 的 AST 转换。如果设置,则 AST 转换将执行类型推断并将类型信息存储在 AST 节点的元数据中。最终,如果发现错误,它将通过专用的 addStaticTypeError 方法将错误添加到编译器中,该方法基本上与传统的 addError 方法相同,但消息前面会带有“静态类型检查”消息。这样做是为了帮助开发人员确定他们看到的错误是“纯 Groovy”错误,还是 STC 模式抛出的错误。 ==== StaticTypeCheckingTestCase 类 必须测试静态类型检查行为。由于需要进行大量可能的检查,因此基本测试类提供了一个测试此模式的框架。静态类型检查的单元测试应覆盖此类别。 === 已做出的决定 ==== 关于本节 本节的目标是提供代码示例,演示 STC 转换实际会在哪些情况下抱怨以及预期的错误消息,并作为未来 STC 文档的基础。本节可能不是最新的,应始终查看 `src/test/groovy/transform/stc` 目录中的 STC 单元测试。 [options="header"] |=== | 功能 | 示例 | 行为 | 状态 | 方法不存在 a| ---- def method() { ... } methode() // 拼写错误 ---- | 抱怨未定义的方法 | 已实现 | 属性不存在 a| ---- class A { int x } A obj = new A() a.y = 2 ---- | 抱怨未定义的属性“y” | 已实现 | 赋值类型检查 a| ---- int x = 2 x = 'String' ---- | 禁止将 String 赋值给 int | 已实现 | 不兼容的二进制表达式 a| ---- 1 + 'string' ---- | 检查二进制表达式的参数是否兼容(此处,没有可用的“plus”方法) | 已实现 | 可能的精度损失 (1/2) a| ---- long myLong = ... int myInt = myLong ---- | 抱怨可能的精度损失 | 已实现 | 可能的精度损失 (2/2) a| ---- int myInt = 2L ---- | 不会抱怨,因为“2”可以表示为 int | 已实现 | 数组组件 a| ---- String[] arr = { '1', '2', '3' } arr[2] = 200 ---- | 无法将 int 值分配给 String[] 类型的数组 | 已实现 | 方法返回类型检查 a| ---- String method() { 'Hello' } int x = method() // 返回类型不匹配 ---- | 确保赋值与方法返回类型兼容 | 已实现 | 显式返回类型检查 a| ---- int method() { return 'String' // 返回类型不兼容 } ---- | 确保返回的值与声明的返回类型兼容 | 已实现 | 隐式返回类型检查 a| ---- int method() { 'String' // 返回类型不兼容 } ---- | 确保返回的值与声明的返回类型兼容 | 已实现 | 隐式 toString() a| ---- String method(String name) { StringBuilder sb = new StringBuilder() sb 'Hi ' << name << '!' } ---- | 隐式调用 toString() | 已实现 | 基本类型推断 a| ---- def str = 'My string' str.toUpperCase() // 'str' 的类型被推断 ---- | 方法调用以及属性访问都根据推断的类型进行检查 | 已实现 | 基本流分析 a| ---- def o ... if (o instanceof String) { o.toUpperCase() // 无需显式转换 } ---- | 当类型可以从之前的 instanceof 检查中推断出来时,无需进行转换 | 已实现 | DefaultGroovyMethods 支持 a| ---- '123'.toInteger() // toInteger() 是 Groovy 扩展方法 ---- | 方法调用可以针对 Groovy 扩展方法进行解析 | 已实现 | with a| ---- class A { int x } def a = new A() a.with { x = 1 } ---- | 方法调用可以针对 Groovy 扩展方法进行解析 | 已实现 | 类别 a| ---- use (MyStringCategory) { 'string'.methodInStringCategory() } ---- | 编译器应该知道扩展方法是在一个类别中找到的 | *不适用*(支持将受到限制,因为类别支持本质上是动态的) | Groovy 列表构造函数 a| ---- Dimension d = [100, 200] ---- | 类型检查参数和参数数量 | 已实现 | Groovy 映射构造函数 a| ---- Bean myBean = [x: 100, y: 200] ---- | 类型检查属性并检查不正确的属性名称 | 已实现 | 闭包参数类型 a| ---- def closure = { int x, int y -> x + y } closure(1, 2) closure('1', '2') // 抱怨 ---- | 调用闭包时对参数进行类型检查 | 已实现 | 闭包返回类型推断 a| ---- def closure = { int x, int y -> x + y } int sum = closure(1, 2) ---- | 闭包返回类型可以从块中推断出来 | 已实现 | 方法返回类型推断 a| ---- def method(int x, int y) { x + y } int sum = method(1, 2) ---- | 如果方法本身使用 @TypeChecked 注解(或类使用 @TypeChecked 注解),则可以从方法中推断返回类型 | 已实现 | 多个赋值 a| ---- def (x, y) = [1, 2] ---- | 在内联声明的情况下,类型检查参数 | 已实现 | 从变量进行多个赋值 a| ---- def (x, y) = list ---- | 在内联声明的情况下,类型检查参数 | 已实现 | 泛型 a| ---- Listlist = [] Listlist = ['a', 'b', 'c'] Listlist = [1, 2, 3] // 应该抛出错误 ---- | 泛型参数的类型检查 | 已实现 | 展开运算符 a| ---- def list = ['a', 'b', 'c'] list*.toUpperCase() ---- | 根据组件类型进行类型检查 | 已实现 | 闭包共享变量 a| ---- def x = new Date() def cl = { x = 'hello' } cl() x.toUpperCase() // 应该抛出错误,因为 toUpperCase() 方法不属于 Date 和 String 类 ---- | 类型检查闭包共享变量的赋值。类型检查器需要执行两遍验证,以检查闭包共享变量上的方法调用是否属于所有赋值类型的最低上界。 | 已实现 |=== === 开放讨论 ==== 闭包参数类型推断 在当前版本的检查器中,像以下这样的惯用结构: ``` ['a','b','c'].collect { it.toUpperCase() } ``` 未能正确识别。您必须在闭包内部显式设置“it”参数的类型。这是因为闭包的预期参数类型在编译时是未知的。目前正在讨论如何将此类型信息添加到源代码中,以便推断引擎能够正确处理它们。闭包参数类型推断的实现需要更改方法签名。它可能不会属于类型检查器的初始版本。 ==== 统一类型 在例如 `x instanceof A || x instanceof B` 的情况下,A 和 B 不相关,我们仍然可以创建一种人工联合类型,其中包含 A 和 B 中存在的所有内容,以允许这些类型的方法调用。此替代方案是仅允许 Object 中的方法,这不太有趣。这种类型化也可以用于多重捕获,确保方法调用仅在其存在于多重捕获的每个异常上时才有效。在当前的实现(2011-10-14)中,多重捕获在 @TypeChecked 检查时已经展开。这意味着这实际上已经代表了一种联合类型,因为相同的代码在每个 catch 块中,因此如果方法在每个类型上都不可用,则方法调用将失败。因此,建议的行为是将 instanceof 情况与多重捕获对齐。 == 参考资料和有用链接 * https://web.archive.org/web/20150508041021/http://docs.codehaus.org/display/GroovyJSR/GEP+8+-+Static+type+checking[GEP-8: 静态类型检查](带有注释的网页存档链接) * http://blackdragsview.blogspot.com/2011/10/flow-sensitive-typing.html[流敏感类型化?] * https://web.archive.org/web/20150508040745/http://www.jroller.com/melix/entry/groovy_static_type_checker_status[Groovy 静态类型检查器:状态更新](网页存档) === 邮件列表讨论 * https://marc.info/?l=groovy-user&m=131805623302039&w=2[groovy-user: [grumpy mode\] 赋值时怎么办?] 讨论 STC 在赋值时检测到潜在错误(例如,隐式数字转换可能导致精度损失)时的预期行为 === JIRA 问题 * https://issues.apache.org/jira/browse/GROOVY-5073[GROOVY-5073: GEP-8 - 静态类型检查] * https://issues.apache.org/jira/browse/GROOVY-3014[GROOVY-3014: 添加一个注解,强制编译器在编译时检查方法是否存在] * https://issues.apache.org/jira/browse/GROOVY-5081[GROOVY-5081: 处理显式和隐式返回] == 更新历史 8 (2012-02-21):: 从 Codehaus wiki 提取的版本 9 (2018-10-16):: 许多细微调整

GEP-8