类型检查
这一阶段对应于代码中的 Typer,具体任务是:分析程序中的每一个表达式和语句,检查其是否类型正确,并构造出 TypedTree 上的对应结点。
自顶向下类型检查
一种经典的类型检查过程按照语法树的结构自顶向下,并依据一系列归纳定义的定型规则 (typing rules) 来进行检查。 考虑一个很简单的算术表达式语言:
expr ::= expr '+' expr | n | s其中 n 指一个整数常量,s 指一个字符串常量。我们设计如下两条定型规则:
T-Int 规则:任何整数常量具有整数类型。
T-Add 规则:若表达式
e1具有整数类型,且表达式e2具有整数类型,那么表达式e1 + e2具有整数类型。
对于表达式 2 + 4 进行类型检查的过程如下:
根据 T-Int 规则,
2具有整数类型。根据 T-Int 规则,
4具有整数类型。由以上两条,根据 T-Add 规则,
2 + 4具有整数类型。
定型规则未定义的视为类型错误,例如我们无法根据上述两条定型规则来推断 1 + "str" 的类型,那么它就无类型 (ill-typed)。
上述定型规则是用自然语言描述的,这是“非形式化”且可能不严格的。 一个严谨的编程语言,需要用形式化系统来精确刻画其定型规则。 对此感兴趣的同学,可从经典教材 Types and Programming Languages 入门。
虽然 Decaf 语言的定型规则比上面的玩具例子要复杂得多,但是在已知定型规则的情况下编码实现类型检查算法并不困难——你只需把规则逐条翻译成代码。
这个观点也不是放之四海而皆准,因为在很多类型系统特别花哨(如依赖类型)的语言中,定型规则往往高度不确定 (non-deterministic),甚至还需要进行(有可能还是不可判定的)逻辑推理。
特别机制
此外,框架代码还实现了以下两种特别的机制,以得出更友好和更有用的错误信息。
类型猜测
根据定型规则,例如我们在分析一个“大”表达式的类型之前,会先分析出其中各“小”表达式的类型。 对于无法得出正确类型的表达式,我们会尽量先推测出一个合理的类型,即使类型检查不通过,也把推测的类型标注在 AST 结点上,而不总是标注 ERROR。 因为当用户修复这个表达式后,那么它的类型就应该是推测出来的类型。例如 instanceof 表达式,只要正确,必然是 bool 类型。 这样,即使 instanceof 是错的,包含它的更“大”的表达式仍然可以假设它是 bool 来继续分析。这样我们可以得出更多有意义的结果。
防止错误扩散
另一方面,如果“小”表达式已经被标注为 ERROR,那么“大”表达式显然类型检查也通过不了。 为了避免那个“小”表达式的错误扩散到所有包含它的“大”表达式中,我们会特判这种情形,不重复报错。 但是,若这个“大”表达式的其他部分产生了新的错误,则依然需要报告这些新的错误。
语句检查
在 Decaf 中,有关语句的类型检查主要分为两个部分——对语句中出现的表达式进行类型检查,和记录该语句是否返回了值。前者的必要性不言自明。 后者是为了保证所有声明了返回类型(非 void)的方法都一定会返回一个值,且通过 return 语句的类型检查,这个值必然是所声明的返回类型(的一个子类型)。
定型规则
各语句对其中出现的表达式的类型有如下要求:
赋值语句中,右值类型是左值类型的子类型
if/while/for 语句中,条件表达式是 bool 类型
return 语句返回值类型是方法返回类型的子类型
print 语句表达式的类型只能是 int, bool 或 string
此外,break 语句必须出现在循环体中。
返回值规则
除以下给出的规则外,其他情况下的语句均不返回值:
一个 if 语句返回值,当且仅当它的 false 分支存在,且两个分支均返回了值
一个 return 语句返回值,当且仅当其返回值对应的表达式存在
一个语句块返回值,当且仅当它非空,且最后一条语句返回了值
表达式的类型检查
表达式只需做类型检查。为简单,我们用记号 e : t 表示表达式 e 的类型为 t,若我们猜测 e 的类型也为 t,则记为 e : !t。 定型规则和猜测机制如下:
常量表达式:具有该常量的类型(int, bool, string, null)
ReadInt() : intReadLine() : string一元取反表达式:
!e : !booliffe : bool一元取负表达式:
-e : !intiffe : int二元算术表达式(
op属于+,-,*,/,%):e1 op e2 : !intiffe1 : intande2 : int二元逻辑表达式(
op属于&&,||):e1 op e2 : !booliffe1 : boolande2 : bool二元比较表达式(
op属于<,<=,>,>=):e1 op e2 : !booliffe1 : intande2 : int二元判等表达式(
op属于==,!=):e1 op e2 : !boolifft1 <: t2ort2 <: t1givene1 : t1ande2 : t2new t[] : t[]ifft !== voidnew A() : class A若A有定义this : class A若当前方法是成员方法且定义在类A中x : t若x是当前作用域有定义的符号且类型为tobj.x : t若obj : class A,x对当前作用域可见且类型为ta[e] : !tgivena : t[], iffe : inta.length() : intiffa : t[]若
e1 : t1, ..., en : tn,则f(e1, ..., en) : tifff : (s1, ..., sn) => s,t1 <: s1, ...,tn <: sn,s <: tinstanceof(e, class A) : !bool若e是一个对象且A有定义(class A) e : class A若A有定义
这里我们引用易于普通人理解的自然语言描述了定型规则,今后我们可能会制定出更加形式化的规范。
Last updated
Was this helpful?