本文由知乎网友“漫慢忙”翻译自官方博客 《New Diagnostic Architecture Overview》
诊断程序(Diagnostics)在编程语言体验中扮演着非常重要的角色。开发人员在编写代码时非常关心的一点是:编译器可以在任何情况下(尤其是代码不完整或无效时)提供适当的指导并指出问题。
在此博客文章中,我们想分享一些即将推出的 Swift 5.2 的重要更新,以改进新版本的的诊断功能。这包括编译器诊断故障的新策略,该策略最初是 Swift 5.1 发行版的一部分,其引入了一些令人兴奋的新结果并改进了错误消息。
挑战
Swift 是一种具有丰富表现力的语言,它有丰富的类型系统,这个系统有许多特性,例如类继承,协议一致性,泛型和重载。尽管作为程序员,我们会竭尽全力编写格式良好的代码,但有时我们需要一点帮助。幸运的是,编译器知道什么样的 Swift 代码是有效的或者无效的。问题是如何更好地告诉您出了什么问题,问题在哪以及如何解决。
编译器做了许多事情来确保程序的正确性,但是这项工作的重点一直是改进类型检查器。Swift 类型检查器强制执行有关如何在源代码中使用类型的规则,并在你违反了这些规则时告诉你。
例如以下代码:
struct S<T> {
init(_: [T]) {}
}
var i = 42
_ = S<Int>([i!])
复制代码
会产生以下诊断结果:
error: type of expression is ambiguous without more context
复制代码
尽管这个诊断结果指出了真正的错误,但由于它不明确,因此并没有太大的帮助。这是因为旧的类型检查器主要用来猜测错误的确切位置。这在许多情况下都有效,但是用户仍然会出现很多无法准确识别的编程错误。为了解决这个问题,我们正在开发一种新的诊断架构。类型检查器不再是在猜测错误发生的位置,而是尝试在遇到问题时“修复”问题,并记住所应用的修复措施。这不仅使类型检查器可以查明更多种类的程序中的错误,也使它能够提前暴露更多的故障。
类型推断概述
由于新的诊断框架与类型检查器紧密结合,因此我们需要先讨论一下类型推断。请注意,这里只是简单地介绍一下。有关类型检查更多详细信息,请参阅 compiler’s documentation on the type checker[1]。
Swift 使用基于约束的类型检查器实现双向类型推断,这使人联想到经典的 Hindley-Milner
[2] 类型推断算法
[3]:
• 类型检查器将源代码转换为约束系统,该约束系统表示代码中类型之间的关系。
• 类型关系是通过类型约束表达的,类型约束要么对单个类型提出要求(例如,它是整数字面量类型),要么将两种类型相关联(例如,一种类型可以转换为另一种类型)。
• 约束中描述的类型可以是 Swift 类型系统中的任何类型,包括元组类型、函数类型、枚举/结构/类类型、协议类型和泛型类型。此外,类型可以是表示为 $<name>
的类型变量。
• 类型变量可以在任何其他类型中使用,例如,类型变量 $Foo
在元组类型 ($Foo,Int)
中使用。
约束系统执行三步操作:
• 产生约束
• 求解约束
• 应用解决方案
诊断过程关注的阶段是约束生成和求解。
给定输入表达式(有时还包括其他上下文信息),约束求解器将生成以下信息:
• 一组类型变量,代表每个子表达式的抽象类型
• 一组描述这些类型变量之间关系的类型约束
最常见的约束类型是二进制约束(binary constraint),它涉及两种类型,可以表示为:
type1 <constraint kind> type2
复制代码
常用的二进制约束有:
• $X <bind to> Y
- 将类型变量 $X
绑定到固定类型 Y
• X <convertible to> Y
- 转换约束要求第一个类型 X
可转换为第二个 Y
,其中包括子类型和等价形式
• X <conforms to> Y
- 指定第一种类型 X
必须符合协议 Y
• (Arg1,Arg2,...) → Result <applicable to> $Function
- “适用函数(applicable function)”约束要求两种类型都是具有相同输入和输出类型的函数类型
约束生成完成后,求解程序将尝试为约束系统中的每个类型变量分配具体类型,并生成满足所有约束的解决方案。
让我们来看看下面的例子:
func foo(_ str: String) {
str + 1
}
复制代码
对于我们来说,很快就能发现表达式 str + 1
存在问题以及该问题所在的位置,但是类型推断引擎只能依靠约束简化算法来确定问题所在。
正如我们之前讨论的,约束求解器首先为 str
,1
和 +
生成约束。输入表达式的每个不同子元素(如str)均由以下方式表示:
• 具体类型(提前知道)
• 用 $<name>
表示的类型变量,可以假定满足与之关联的约束的任何类型。
约束生成阶段完成后,表达式 str + 1
的约束系统将具有类型变量和约束的组合。接下来让我们来看一下。
类型变量
• $Str
表示变量 str 的类型,它是 +
调用中的第一个参数
• $One
代表文字 1
的类型,它是 +
调用中的第二个参数
• $Result
表示对运算符 +
调用的结果类型
• $Plus
代表运算符 +
本身的类型,它是一组重载方法的集合。